Browse Source

dynamite: Support per operation security

pull/232/head
jld3103 2 years ago
parent
commit
2fa0758e3e
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 4
      packages/dynamite/lib/src/models/operation.dart
  2. 9
      packages/dynamite/lib/src/models/operation.g.dart
  3. 263
      packages/dynamite/lib/src/openapi_builder.dart

4
packages/dynamite/lib/src/models/operation.dart

@ -2,6 +2,7 @@ import 'package:dynamite/src/models/parameter.dart';
import 'package:dynamite/src/models/request_body.dart'; import 'package:dynamite/src/models/request_body.dart';
import 'package:dynamite/src/models/response.dart'; import 'package:dynamite/src/models/response.dart';
import 'package:dynamite/src/models/responses.dart'; import 'package:dynamite/src/models/responses.dart';
import 'package:dynamite/src/models/security_requirement.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'operation.g.dart'; part 'operation.g.dart';
@ -17,6 +18,7 @@ class Operation {
this.parameters, this.parameters,
this.requestBody, this.requestBody,
this.responses, this.responses,
this.security,
}); });
factory Operation.fromJson(final Map<String, dynamic> json) => _$OperationFromJson(json); factory Operation.fromJson(final Map<String, dynamic> json) => _$OperationFromJson(json);
@ -37,4 +39,6 @@ class Operation {
final RequestBody? requestBody; final RequestBody? requestBody;
final Responses? responses; final Responses? responses;
final List<SecurityRequirement>? security;
} }

9
packages/dynamite/lib/src/models/operation.g.dart

@ -17,7 +17,8 @@ Operation _$OperationFromJson(Map<String, dynamic> json) {
'tags', 'tags',
'parameters', 'parameters',
'requestBody', 'requestBody',
'responses' 'responses',
'security'
], ],
); );
return Operation( return Operation(
@ -32,6 +33,11 @@ Operation _$OperationFromJson(Map<String, dynamic> json) {
responses: (json['responses'] as Map<String, dynamic>?)?.map( responses: (json['responses'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, Response.fromJson(e as Map<String, dynamic>)), (k, e) => MapEntry(k, Response.fromJson(e as Map<String, dynamic>)),
), ),
security: (json['security'] as List<dynamic>?)
?.map((e) => (e as Map<String, dynamic>).map(
(k, e) => MapEntry(k, (e as List<dynamic>).map((e) => e as String).toList()),
))
.toList(),
); );
} }
@ -52,5 +58,6 @@ Map<String, dynamic> _$OperationToJson(Operation instance) {
writeNotNull('parameters', instance.parameters?.map((e) => e.toJson()).toList()); writeNotNull('parameters', instance.parameters?.map((e) => e.toJson()).toList());
writeNotNull('requestBody', instance.requestBody?.toJson()); writeNotNull('requestBody', instance.requestBody?.toJson());
writeNotNull('responses', instance.responses?.map((k, e) => MapEntry(k, e.toJson()))); writeNotNull('responses', instance.responses?.map((k, e) => MapEntry(k, e.toJson())));
writeNotNull('security', instance.security);
return val; return val;
} }

263
packages/dynamite/lib/src/openapi_builder.dart

@ -55,7 +55,7 @@ class OpenAPIBuilder implements Builder {
: a.compareTo(b), : a.compareTo(b),
); );
final hasAnySecurity = spec.security?.isNotEmpty ?? false; final hasAnySecurity = spec.components?.securitySchemes?.isNotEmpty ?? false;
final state = State(prefix); final state = State(prefix);
final output = <String>[ final output = <String>[
@ -255,128 +255,152 @@ class OpenAPIBuilder implements Builder {
(final b) => b (final b) => b
..name = '${prefix}Authentication' ..name = '${prefix}Authentication'
..abstract = true ..abstract = true
..methods.add( ..methods.addAll([
Method(
(final b) => b
..name = 'id'
..type = MethodType.getter
..returns = refer('String'),
),
Method( Method(
(final b) => b (final b) => b
..name = 'headers' ..name = 'headers'
..type = MethodType.getter ..type = MethodType.getter
..returns = refer('Map<String, String>'), ..returns = refer('Map<String, String>'),
), ),
), ]),
).accept(emitter).toString(), ).accept(emitter).toString(),
], ],
]; ];
if (spec.security != null) { if (spec.components?.securitySchemes != null) {
for (final securityRequirement in spec.security!) { for (final name in spec.components!.securitySchemes!.keys) {
for (final name in securityRequirement.keys) { final securityScheme = spec.components!.securitySchemes![name]!;
final securityScheme = spec.components!.securitySchemes![name]!; switch (securityScheme.type) {
switch (securityScheme.type) { case 'http':
case 'http': switch (securityScheme.scheme) {
switch (securityScheme.scheme) { case 'basic':
case 'basic': output.add(
output.add( Class(
Class( (final b) {
(final b) { final fields = ['username', 'password'];
final fields = ['username', 'password']; b
b ..name = '${prefix}HttpBasicAuthentication'
..name = '${prefix}HttpBasicAuthentication' ..extend = refer('${prefix}Authentication')
..extend = refer('${prefix}Authentication') ..constructors.add(
..constructors.add( Constructor(
Constructor( (final b) => b
(final b) => b ..optionalParameters.addAll(
..optionalParameters.addAll( fields.map(
fields.map( (final name) => Parameter(
(final name) => Parameter(
(final b) => b
..name = name
..toThis = true
..named = true
..required = true,
),
),
),
),
)
..fields.addAll(
fields.map(
(final name) => Field(
(final b) => b
..name = name
..type = refer('String')
..modifier = FieldModifier.final$,
),
),
)
..methods.add(
Method(
(final b) => b
..name = 'headers'
..type = MethodType.getter
..returns = refer('Map<String, String>')
..lambda = true
..body = const Code(r'''
{
'Authorization': 'Basic ${base64.encode(utf8.encode('$username:$password'))}',
}
'''),
),
);
},
).accept(emitter).toString(),
);
continue;
case 'bearer':
output.add(
Class(
(final b) {
b
..name = '${prefix}HttpBearerAuthentication'
..extend = refer('${prefix}Authentication')
..constructors.add(
Constructor(
(final b) => b
..optionalParameters.add(
Parameter(
(final b) => b (final b) => b
..name = 'token' ..name = name
..toThis = true ..toThis = true
..named = true ..named = true
..required = true, ..required = true,
), ),
), ),
), ),
) ),
..fields.add( )
Field( ..fields.addAll(
fields.map(
(final name) => Field(
(final b) => b (final b) => b
..name = 'token' ..name = name
..type = refer('String') ..type = refer('String')
..modifier = FieldModifier.final$, ..modifier = FieldModifier.final$,
), ),
) ),
..methods.add( )
Method( ..methods.addAll([
(final b) => b Method(
..name = 'headers' (final b) => b
..type = MethodType.getter ..name = 'id'
..returns = refer('Map<String, String>') ..annotations.add(refer('override'))
..lambda = true ..type = MethodType.getter
..body = const Code(r''' ..lambda = true
..returns = refer('String')
..body = Code("'$name'"),
),
Method(
(final b) => b
..name = 'headers'
..annotations.add(refer('override'))
..type = MethodType.getter
..returns = refer('Map<String, String>')
..lambda = true
..body = const Code(r'''
{
'Authorization': 'Basic ${base64.encode(utf8.encode('$username:$password'))}',
}
'''),
),
]);
},
).accept(emitter).toString(),
);
continue;
case 'bearer':
output.add(
Class(
(final b) {
b
..name = '${prefix}HttpBearerAuthentication'
..extend = refer('${prefix}Authentication')
..constructors.add(
Constructor(
(final b) => b
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'token'
..toThis = true
..named = true
..required = true,
),
),
),
)
..fields.addAll([
Field(
(final b) => b
..name = 'token'
..type = refer('String')
..modifier = FieldModifier.final$,
),
])
..methods.addAll([
Method(
(final b) => b
..name = 'id'
..annotations.add(refer('override'))
..type = MethodType.getter
..lambda = true
..returns = refer('String')
..body = Code("'$name'"),
),
Method(
(final b) => b
..name = 'headers'
..annotations.add(refer('override'))
..type = MethodType.getter
..returns = refer('Map<String, String>')
..lambda = true
..body = const Code(r'''
{ {
'Authorization': 'Bearer $token', 'Authorization': 'Bearer $token',
} }
'''), '''),
), ),
); ]);
}, },
).accept(emitter).toString(), ).accept(emitter).toString(),
); );
continue; continue;
} }
}
throw Exception('Can not work with security scheme ${securityScheme.toJson()}');
} }
throw Exception('Can not work with security scheme ${securityScheme.toJson()}');
} }
} }
@ -438,8 +462,8 @@ class OpenAPIBuilder implements Builder {
if (hasAnySecurity) ...[ if (hasAnySecurity) ...[
Field( Field(
(final b) => b (final b) => b
..name = 'authentication' ..name = 'authentications'
..type = refer('${prefix}Authentication?') ..type = refer('List<${prefix}Authentication>')
..modifier = FieldModifier.final$, ..modifier = FieldModifier.final$,
), ),
], ],
@ -482,22 +506,18 @@ class OpenAPIBuilder implements Builder {
if (hasAnySecurity) ...[ if (hasAnySecurity) ...[
Parameter( Parameter(
(final b) => b (final b) => b
..name = 'authentication' ..name = 'authentications'
..toThis = true ..toThis = true
..named = true, ..named = true
..defaultTo = const Code('const []'),
), ),
], ],
]) ])
..body = Code(''' ..body = const Code('''
this.baseHeaders = { this.baseHeaders = {
if (baseHeaders != null) ...{ if (baseHeaders != null) ...{
...baseHeaders, ...baseHeaders,
}, },
${hasAnySecurity ? '''
if (authentication != null) ...{
...authentication!.headers,
},
''' : ''}
}; };
this.httpClient = (httpClient ?? HttpClient())..userAgent = userAgent; this.httpClient = (httpClient ?? HttpClient())..userAgent = userAgent;
'''), '''),
@ -651,6 +671,33 @@ class OpenAPIBuilder implements Builder {
Uint8List? body; Uint8List? body;
'''); ''');
final securityRequirements =
(operation.security ?? spec.security) ?? <Map<String, List<String>>>[];
final isOptionalSecurity =
securityRequirements.where((final requirement) => requirement.keys.isEmpty).isNotEmpty;
if (isOptionalSecurity) {
code.write('''
if (${isRootClient ? '' : 'rootClient.'}authentications.isNotEmpty) {
headers.addAll(${isRootClient ? '' : 'rootClient.'}authentications.first.headers);
}
''');
} else {
for (final requirement in securityRequirements) {
code.write('''
if (${isRootClient ? '' : 'rootClient.'}authentications.map((final a) => a.id).contains('${requirement.keys.single}')) {
headers.addAll(${isRootClient ? '' : 'rootClient.'}authentications.singleWhere((final a) => a.id == '${requirement.keys.single}').headers);
} else
''');
}
if (securityRequirements.isNotEmpty) {
code.write('''
{
throw Exception('Missing authentication for ${securityRequirements.map((final r) => r.keys.single).join(' or ')}');
}
''');
}
}
for (final parameter in parameters) { for (final parameter in parameters) {
final nullable = _isParameterNullable( final nullable = _isParameterNullable(
parameter.required, parameter.required,

Loading…
Cancel
Save