A LOT of work necessary...
This commit is contained in:
parent
f8c59c98dc
commit
9a8f5d1111
13 changed files with 180 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
||||||
.packages
|
.packages
|
||||||
.project
|
.project
|
||||||
.pub/
|
.pub/
|
||||||
|
.scripts-bin/
|
||||||
build/
|
build/
|
||||||
**/packages/
|
**/packages/
|
||||||
|
|
||||||
|
|
BIN
lib/.DS_Store
vendored
Normal file
BIN
lib/.DS_Store
vendored
Normal file
Binary file not shown.
0
lib/angel_auth_oauth2_server.dart
Normal file
0
lib/angel_auth_oauth2_server.dart
Normal file
8
lib/src.old/authorization_request.dart
Normal file
8
lib/src.old/authorization_request.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import 'grant_type.dart';
|
||||||
|
|
||||||
|
/// An authorization grant is a credential representing the resource
|
||||||
|
/// owner's authorization (to access its protected resources) used by the
|
||||||
|
/// client to obtain an access token.
|
||||||
|
abstract class AuthorizationRequest {
|
||||||
|
GrantType get type;
|
||||||
|
}
|
5
lib/src.old/authorization_server.dart
Normal file
5
lib/src.old/authorization_server.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/// The server issuing access tokens to the client after successfully
|
||||||
|
/// authenticating the resource owner and obtaining authorization.
|
||||||
|
abstract class AuthorizationServer {
|
||||||
|
|
||||||
|
}
|
9
lib/src.old/client.dart
Normal file
9
lib/src.old/client.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'authorization_request.dart';
|
||||||
|
|
||||||
|
/// An application making protected resource requests on behalf of the
|
||||||
|
/// resource owner and with its authorization. The term "client" does
|
||||||
|
/// not imply any particular implementation characteristics (e.g.,
|
||||||
|
/// whether the application executes on a server, a desktop, or other
|
||||||
|
/// devices).
|
||||||
|
abstract class Client extends Stream<AuthorizationRequest> {}
|
16
lib/src.old/grant_type.dart
Normal file
16
lib/src.old/grant_type.dart
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/// The four grant types defined in the OAuth2 specification.
|
||||||
|
enum GrantType {
|
||||||
|
AUTHORIZATION_CODE,
|
||||||
|
IMPLICIT,
|
||||||
|
RESOURCE_OWNER_PASSWORD_CREDENTIALS,
|
||||||
|
CLIENT_CREDENTIALS,
|
||||||
|
// TODO: OTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `String` representations of the four main grant types.
|
||||||
|
const Map<GrantType, String> GRANT_TYPES = const {
|
||||||
|
GrantType.AUTHORIZATION_CODE: 'authorization_code',
|
||||||
|
GrantType.IMPLICIT: 'implicit'
|
||||||
|
// TODO: RESOURCE_OWNER_PASSWORD_CREDENTIALS
|
||||||
|
// TODO: CLIENT_CREDENTIALS
|
||||||
|
};
|
13
lib/src.old/grants/authorization_code.dart
Normal file
13
lib/src.old/grants/authorization_code.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import '../authorization_request.dart';
|
||||||
|
import '../grant_type.dart';
|
||||||
|
|
||||||
|
/// The authorization code is obtained by using an authorization server
|
||||||
|
/// as an intermediary between the client and resource owner. Instead of
|
||||||
|
/// requesting authorization directly from the resource owner, the client
|
||||||
|
/// directs the resource owner to an authorization server (via its
|
||||||
|
/// user-agent as defined in [RFC2616]), which in turn directs the
|
||||||
|
/// resource owner back to the client with the authorization code.
|
||||||
|
class AuthorizationCodeGrantRequest implements AuthorizationRequest {
|
||||||
|
@override
|
||||||
|
GrantType get type => GrantType.AUTHORIZATION_CODE;
|
||||||
|
}
|
6
lib/src.old/resource_owner.dart
Normal file
6
lib/src.old/resource_owner.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/// An entity capable of granting access to a protected resource.
|
||||||
|
/// When the resource owner is a person, it is referred to as an
|
||||||
|
/// end-user.
|
||||||
|
class ResourceOwner {
|
||||||
|
|
||||||
|
}
|
5
lib/src.old/resource_server.dart
Normal file
5
lib/src.old/resource_server.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/// The server hosting the protected resources, capable of accepting
|
||||||
|
/// and responding to protected resource requests using access tokens.
|
||||||
|
abstract class ResourceServer {
|
||||||
|
|
||||||
|
}
|
104
lib/strategy.dart
Normal file
104
lib/strategy.dart
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:angel_auth/angel_auth.dart';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
|
||||||
|
abstract class Oauth2ServerStrategy extends AuthStrategy {
|
||||||
|
@override
|
||||||
|
final String name = 'oauth2-server';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> canLogout(RequestContext req, ResponseContext res) async => true;
|
||||||
|
|
||||||
|
/// Convey to the user that one or more fields are missing.
|
||||||
|
///
|
||||||
|
/// [field] can be a single value, or an `Iterable`.
|
||||||
|
AngelHttpException missingField(field) {
|
||||||
|
Iterable<String> fields =
|
||||||
|
field is Iterable ? field.map((x) => x.toString()) : [field.toString()];
|
||||||
|
|
||||||
|
if (field == null)
|
||||||
|
throw new ArgumentError.notNull('field');
|
||||||
|
else if (fields.isEmpty)
|
||||||
|
throw new ArgumentError(
|
||||||
|
'Cannot provide an empty list of missing fields.');
|
||||||
|
|
||||||
|
return new AngelHttpException.badRequest(
|
||||||
|
message:
|
||||||
|
"Missing one or more of the following fields: " + fields.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a map containing the given values of all [required] keys,
|
||||||
|
/// or throws a [missingField] error if any are missing.
|
||||||
|
Map<String, String> ensureAll(RequestContext req, Iterable<String> required) {
|
||||||
|
if (required.any((str) =>
|
||||||
|
!req.body.containsKey(str) ||
|
||||||
|
(req.body[str] is! String && req.body[str].isEmpty)))
|
||||||
|
throw missingField(required);
|
||||||
|
|
||||||
|
return required
|
||||||
|
.fold(<String, String>{}, (map, key) => map..[key] = req.query[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future authenticate(RequestContext req, ResponseContext res,
|
||||||
|
[AngelAuthOptions options]) {
|
||||||
|
if (req.body['response_type'] is! String) {
|
||||||
|
return new Future.error(missingField('response_type'));
|
||||||
|
} else {
|
||||||
|
String responseType = req.body['response_type'];
|
||||||
|
|
||||||
|
switch (responseType) {
|
||||||
|
case 'code':
|
||||||
|
return authorizationCodeGrant(req, res);
|
||||||
|
case 'authorization_code':
|
||||||
|
return accessTokenRequest(req, res);
|
||||||
|
default:
|
||||||
|
throw new AngelHttpException.badRequest(
|
||||||
|
message: "Unsupported grant type '$responseType'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates an authorization code for a client who is requesting access.
|
||||||
|
Future<String> createAuthorizationCode(
|
||||||
|
String clientId, String redirectUri, String scope, String state);
|
||||||
|
|
||||||
|
/// Generates a redirect URL for a client who is requesting access via
|
||||||
|
/// an authorization code grant request.
|
||||||
|
///
|
||||||
|
/// Do not include a query component, or trailing slashes.
|
||||||
|
Future<String> createRedirectUrl(
|
||||||
|
String clientId, String redirectUri, String scope, String state);
|
||||||
|
|
||||||
|
/// Performs an authorization code grant.
|
||||||
|
Future authorizationCodeGrant(RequestContext req, ResponseContext res) async {
|
||||||
|
var data = ensureAll(req, ['client_id']);
|
||||||
|
String clientId = data['client_id'];
|
||||||
|
String redirectUri = req.body['redirect_uri'],
|
||||||
|
scope = req.body['scope'],
|
||||||
|
state = req.body['state'];
|
||||||
|
var redirect = await createRedirectUrl(clientId, redirectUri?.toString(),
|
||||||
|
scope?.toString(), state?.toString());
|
||||||
|
|
||||||
|
var code = await createAuthorizationCode(clientId, redirectUri?.toString(),
|
||||||
|
scope?.toString(), state?.toString());
|
||||||
|
|
||||||
|
List<String> query = ['code=' + Uri.encodeQueryComponent(code)];
|
||||||
|
|
||||||
|
if (state?.isNotEmpty == true)
|
||||||
|
query.add('state=' + Uri.encodeQueryComponent(state));
|
||||||
|
|
||||||
|
res.redirect(redirect + '?' + query.join('&'));
|
||||||
|
|
||||||
|
// TODO: Support error responses: https://tools.ietf.org/html/rfc6749#section-4
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Awards an access token to a successfully authenticated user.
|
||||||
|
Future<AccessTokenInfo> accessTokenRequest(
|
||||||
|
RequestContext req, ResponseContext res) async {
|
||||||
|
var data = ensureAll(req, ['code', 'redirect_uri', 'client_id']);
|
||||||
|
String code = data['code'],
|
||||||
|
redirectUri = data['redirect_uri'],
|
||||||
|
clientId = data['client_id'];
|
||||||
|
}
|
||||||
|
}
|
13
pubspec.yaml
Normal file
13
pubspec.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
author: "Tobe O <thosakwe@gmail.com>"
|
||||||
|
description: "angel_auth strategy for in-house OAuth2 login."
|
||||||
|
homepage: "https://github.com/thosakwe/oauth2_server.git"
|
||||||
|
name: "angel_auth_oauth2_server"
|
||||||
|
version: "0.0.0"
|
||||||
|
dependencies:
|
||||||
|
angel_auth: "^1.0.0-dev"
|
||||||
|
angel_framework: "^1.0.0-dev"
|
||||||
|
dev_dependencies:
|
||||||
|
angel_test: "^1.0.0-dev"
|
||||||
|
http: "^0.11.3+9"
|
||||||
|
oauth2: "^1.0.2"
|
||||||
|
test: "^0.12.18+1"
|
Loading…
Reference in a new issue