Skip to content

Commit

Permalink
refactor: rework credentials system
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb committed Mar 27, 2022
1 parent fc792c3 commit 7f3c0ed
Show file tree
Hide file tree
Showing 23 changed files with 159 additions and 137 deletions.
1 change: 0 additions & 1 deletion lib/definitions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@ export 'src/definitions/credentials/credentials.dart';
export 'src/definitions/credentials/digest_credentials.dart';
export 'src/definitions/credentials/oauth2_credentials.dart';
export 'src/definitions/credentials/psk_credentials.dart';
export 'src/definitions/security/credentials_scheme.dart';
export 'src/definitions/thing_description.dart';
export 'src/definitions/thing_model.dart';
21 changes: 7 additions & 14 deletions lib/src/binding_http/http_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ import '../definitions/credentials/bearer_credentials.dart';
import '../definitions/credentials/credentials.dart';
import '../definitions/credentials/digest_credentials.dart';
import '../definitions/form.dart';
import '../definitions/security/basic_security_scheme.dart';
import '../definitions/security/bearer_security_scheme.dart';
import '../definitions/security/credentials_scheme.dart';
import '../definitions/security/digest_security_scheme.dart';
import '../scripting_api/subscription.dart';

const _authorizationHeader = "Authorization";
Expand Down Expand Up @@ -87,9 +83,9 @@ class HttpClient extends ProtocolClient {
final headers = _getHeadersFromForm(form);
_applySecurityToHeader(form, headers);
final BasicCredentials? basicCredentials =
_credentialsFromForm<BasicSecurityScheme>(form) as BasicCredentials?;
_credentialsFromForm<BasicCredentials>(form);
final DigestCredentials? digestCredentials =
_credentialsFromForm<DigestSecurityScheme>(form) as DigestCredentials?;
_credentialsFromForm<DigestCredentials>(form);
switch (requestMethod) {
case HttpRequestMethod.get:
final getMethod =
Expand Down Expand Up @@ -121,13 +117,10 @@ class HttpClient extends ProtocolClient {
}

/// Selects the first instance of defined [Credentials] from a [form].
static Credentials? _credentialsFromForm<T extends CredentialsScheme>(
Form form) {
final securityScheme = form.securityDefinitions.values
.whereType<T>()
.where((element) => element.credentials != null);
if (securityScheme.isNotEmpty) {
return securityScheme.first.credentials;
static T? _credentialsFromForm<T extends Credentials>(Form form) {
final credentials = form.credentials.whereType<T>();
if (credentials.isNotEmpty) {
return credentials.first;
}

return null;
Expand Down Expand Up @@ -205,7 +198,7 @@ class HttpClient extends ProtocolClient {

void _applySecurityToHeader(Form form, Map<String, String> headers) {
final BearerCredentials? bearerCredentials =
_credentialsFromForm<BearerSecurityScheme>(form) as BearerCredentials?;
_credentialsFromForm<BearerCredentials>(form);

if (bearerCredentials != null) {
headers[_authorizationHeader] = "Bearer ${bearerCredentials.token}";
Expand Down
60 changes: 7 additions & 53 deletions lib/src/core/consumed_thing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,9 @@ import 'package:uri/uri.dart';

import '../../scripting_api.dart' as scripting_api;
import '../../scripting_api.dart' hide ConsumedThing, InteractionOutput;
import '../definitions/credentials/apikey_credentials.dart';
import '../definitions/credentials/basic_credentials.dart';
import '../definitions/credentials/bearer_credentials.dart';
import '../definitions/credentials/credentials.dart';
import '../definitions/credentials/digest_credentials.dart';
import '../definitions/credentials/oauth2_credentials.dart';
import '../definitions/credentials/psk_credentials.dart';
import '../definitions/data_schema.dart';
import '../definitions/form.dart';
import '../definitions/interaction_affordances/interaction_affordance.dart';
import '../definitions/security/apikey_security_scheme.dart';
import '../definitions/security/basic_security_scheme.dart';
import '../definitions/security/bearer_security_scheme.dart';
import '../definitions/security/digest_security_scheme.dart';
import '../definitions/security/oauth2_security_scheme.dart';
import '../definitions/security/psk_security_scheme.dart';
import '../definitions/security/security_scheme.dart';
import '../definitions/thing_description.dart';
import 'interaction_output.dart';
import 'operation_type.dart';
Expand Down Expand Up @@ -75,6 +61,9 @@ class ConsumedThing implements scripting_api.ConsumedThing {

final Map<String, scripting_api.Subscription> _observedProperties = {};

/// Determines the id of this [ConsumedThing].
String get identifier => thingDescription.identifier;

/// Constructor
ConsumedThing(this.servient, this.thingDescription)
: title = thingDescription.title {
Expand All @@ -85,36 +74,6 @@ class ConsumedThing implements scripting_api.ConsumedThing {
/// [scheme].
bool hasClientFor(String scheme) => servient.hasClientFor(scheme);

static void _applyCredentials(Map<String, Credentials>? credentialStore,
Map<String, SecurityScheme> securityDefinitions) {
for (final entry in securityDefinitions.entries) {
final credentials = credentialStore?[entry.key];
final securityDefinition = entry.value;
// TODO(JKRhb): Maybe this matching can be done more elegantly.
// TODO(JKRhb): Check whether the SecurityScheme should be referenced by
// the credentials instead.
if (securityDefinition is BasicSecurityScheme &&
credentials is BasicCredentials) {
securityDefinition.credentials = credentials;
} else if (securityDefinition is PskSecurityScheme &&
credentials is PskCredentials) {
securityDefinition.credentials = credentials;
} else if (securityDefinition is DigestSecurityScheme &&
credentials is DigestCredentials) {
securityDefinition.credentials = credentials;
} else if (securityDefinition is ApiKeySecurityScheme &&
credentials is ApiKeyCredentials) {
securityDefinition.credentials = credentials;
} else if (securityDefinition is BearerSecurityScheme &&
credentials is BearerCredentials) {
securityDefinition.credentials = credentials;
} else if (securityDefinition is OAuth2SecurityScheme &&
credentials is OAuth2Credentials) {
securityDefinition.credentials = credentials;
}
}
}

_ClientAndForm _getClientFor(
List<Form> forms,
OperationType operationType,
Expand All @@ -131,14 +90,6 @@ class ConsumedThing implements scripting_api.ConsumedThing {

final int? formIndex = options?.formIndex;

// TODO(JKRhb): Revisit ID determination
final id =
thingDescription.id ?? thingDescription.base ?? thingDescription.title;

final credentials = servient.credentials(id);

_applyCredentials(credentials, thingDescription.securityDefinitions);

if (formIndex != null) {
if (formIndex >= 0 && formIndex < forms.length) {
foundForm = forms[formIndex];
Expand All @@ -156,8 +107,11 @@ class ConsumedThing implements scripting_api.ConsumedThing {
client = servient.clientFor(scheme);
}

final credentials = servient.credentials(identifier);

final form = foundForm.copy()
..href = _resolveUriVariables(interactionAffordance, foundForm, options);
..href = _resolveUriVariables(interactionAffordance, foundForm, options)
..updateCredentials(credentials);

return _ClientAndForm(client, form);
}
Expand Down
61 changes: 61 additions & 0 deletions lib/src/core/servient.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,24 @@ import 'package:uuid/uuid.dart';
import '../definitions/credentials/credentials.dart';
import '../definitions/interaction_affordances/interaction_affordance.dart';
import '../definitions/thing_description.dart';
import 'consumed_thing.dart';
import 'content_serdes.dart';
import 'exposed_thing.dart';
import 'protocol_interfaces/protocol_client.dart';
import 'protocol_interfaces/protocol_client_factory.dart';
import 'protocol_interfaces/protocol_server.dart';
import 'wot.dart';

/// This [Exception] is thrown when the addition or application of [Credentials]
/// fails.
class CredentialsException implements Exception {
/// The error message of this [CredentialsException].
String message;

/// Constructor.
CredentialsException(this.message);
}

// TODO(JKRhb): Documentation should be improved.
/// A software stack that implements the WoT building blocks.
///
Expand All @@ -26,6 +37,7 @@ class Servient {
final List<ProtocolServer> _servers = [];
final Map<String, ProtocolClientFactory> _clientFactories = {};
final Map<String, ExposedThing> _things = {};
final Map<String, ConsumedThing> _consumedThings = {};
final Map<String, Map<String, Credentials>> _credentialsStore = {};

/// The [ContentSerdes] object that is used for serializing/deserializing.
Expand Down Expand Up @@ -108,6 +120,22 @@ class Servient {
return true;
}

/// Adds a [ConsumedThing] to the servient if it hasn't been registered
/// before.
///
/// Returns `false` if the [thing] has already been registered, otherwise
/// `true`.
bool addConsumedThing(ConsumedThing thing) {
final id = thing.identifier;
if (_things.containsKey(id)) {
return false;
}

_consumedThings[id] = thing;
_applyCredentials(id);
return true;
}

/// Returns an [ExposedThing] with the given [id] if it has been registered.
ExposedThing? thing(String id) => _things[id];

Expand Down Expand Up @@ -154,6 +182,7 @@ class Servient {
} else {
currentCredentials[definitionKey] = credentials;
}
_applyCredentials(id);
}

/// Removes [Credentials] from this [Servient].
Expand All @@ -180,4 +209,36 @@ class Servient {
/// Returns null if the [identifier] is unknown.
Map<String, Credentials>? credentials(String identifier) =>
_credentialsStore[identifier];

/// Links the [Credentials] stored in this [Servient] to the `SecuritySchemes`
/// of a [ConsumedThing].
///
/// Throws a [CredentialsException] if a type mismatch between [Credentials]
/// and a `SecurityScheme` should be detected.
void _applyCredentials(String id) {
final consumedThing = _consumedThings[id];

if (consumedThing == null) {
return;
}

final securityDefinitions =
consumedThing.thingDescription.securityDefinitions;

for (final entry in securityDefinitions.entries) {
final credentials = _credentialsStore[id]?[entry.key];
final securityDefinition = entry.value;

final securitySchemeType = securityDefinition.scheme;
final credentialsType = credentials?.securitySchemeType;

if (securitySchemeType == credentialsType) {
credentials?.securityScheme = securityDefinition;
} else if (credentials != null) {
throw CredentialsException("Type mismatch of credentials for"
" thing with id $id! Credentials type was $credentialsType,"
" SecurityScheme type was $securitySchemeType");
}
}
}
}
24 changes: 23 additions & 1 deletion lib/src/core/wot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ import 'exposed_thing.dart';
import 'servient.dart';
import 'thing_discovery.dart' show ThingDiscovery;

/// This [Exception] is thrown if an error during the consumption of a
/// [ThingDescription] occurs.
class ThingConsumptionException implements Exception {
/// The actual error message.
String message;

/// The identifier of the [ThingDescription] that triggered this [Exception].
String identifier;

/// Constructor
ThingConsumptionException(this.message, this.identifier);
}

/// Implementation of the [scripting_api.WoT] runtime interface.
class WoT implements scripting_api.WoT {
final Servient _servient;
Expand All @@ -22,9 +35,18 @@ class WoT implements scripting_api.WoT {
///
/// The returned [ConsumedThing] can then be used to trigger
/// interaction affordances exposed by the Thing.
///
/// If a [ThingDescription] with the same identifier has already been
@override
Future<ConsumedThing> consume(ThingDescription thingDescription) async {
return ConsumedThing(_servient, thingDescription);
final newThing = ConsumedThing(_servient, thingDescription);
if (_servient.addConsumedThing(newThing)) {
return newThing;
} else {
final id = thingDescription.identifier;
throw ThingConsumptionException(
"A ConsumedThing with identifier $id already exists", id);
}
}

/// Exposes a Thing based on a (partial) TD.
Expand Down
4 changes: 3 additions & 1 deletion lib/src/definitions/credentials/apikey_credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
//
// SPDX-License-Identifier: BSD-3-Clause

import '../security/apikey_security_scheme.dart';

import 'credentials.dart';

/// [Credentials] used for the `APIKeySecurityScheme`.
class ApiKeyCredentials extends Credentials {
class ApiKeyCredentials extends Credentials<ApiKeySecurityScheme> {
/// The [apiKey] associated with these [ApiKeyCredentials].
String apiKey;

Expand Down
4 changes: 3 additions & 1 deletion lib/src/definitions/credentials/basic_credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
//
// SPDX-License-Identifier: BSD-3-Clause

import '../security/basic_security_scheme.dart';

import 'credentials.dart';

/// [Credentials] used for the `BasicSecurityScheme`.
///
/// Provides an unencrypted [username] and [password] combination.
class BasicCredentials extends Credentials {
class BasicCredentials extends Credentials<BasicSecurityScheme> {
/// The [username] associated with these [BasicCredentials].
String username;

Expand Down
4 changes: 3 additions & 1 deletion lib/src/definitions/credentials/bearer_credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
//
// SPDX-License-Identifier: BSD-3-Clause

import '../security/bearer_security_scheme.dart';

import 'credentials.dart';

/// [Credentials] used for the `BearerSecurityScheme`.
class BearerCredentials extends Credentials {
class BearerCredentials extends Credentials<BearerSecurityScheme> {
/// The [token] associated with these [BearerCredentials].
String token;

Expand Down
7 changes: 6 additions & 1 deletion lib/src/definitions/credentials/credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
//
// SPDX-License-Identifier: BSD-3-Clause

import '../security/security_scheme.dart';

/// Base class used for defining credentials for Thing Interactions.
abstract class Credentials {
abstract class Credentials<T extends SecurityScheme> {
/// The name of the SecurityScheme these [Credentials] are associated with.
final String securitySchemeType;

/// The concrete [SecurityScheme] linked to these [Credentials].
T? securityScheme;

/// Constructor.
Credentials(this.securitySchemeType);
}
5 changes: 3 additions & 2 deletions lib/src/definitions/credentials/digest_credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
//
// SPDX-License-Identifier: BSD-3-Clause

import '../security/digest_security_scheme.dart';
import 'credentials.dart';

/// [Credentials] used for the `DigestSecurityScheme`.
class DigestCredentials extends Credentials {
/// [Credentials] used for the [DigestSecurityScheme].
class DigestCredentials extends Credentials<DigestSecurityScheme> {
/// The [username] associated with these [DigestCredentials].
String username;

Expand Down
5 changes: 3 additions & 2 deletions lib/src/definitions/credentials/oauth2_credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
//
// SPDX-License-Identifier: BSD-3-Clause

import '../security/oauth2_security_scheme.dart';
import 'credentials.dart';

/// [Credentials] used for the `OAuth2SecurityScheme`.
class OAuth2Credentials extends Credentials {
/// [Credentials] used for the [OAuth2SecurityScheme].
class OAuth2Credentials extends Credentials<OAuth2SecurityScheme> {
/// The optional secret for these [OAuth2Credentials].
String? secret;

Expand Down
Loading

0 comments on commit 7f3c0ed

Please sign in to comment.