Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add authorizationscopes #917

Merged
merged 4 commits into from
Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions samtranslator/model/api/api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ def _add_auth(self):
if authorizers:
swagger_editor.add_authorizers_security_definitions(authorizers)
self._set_default_authorizer(swagger_editor, authorizers, auth_properties.DefaultAuthorizer,
auth_properties.AddDefaultAuthorizerToCorsPreflight)
auth_properties.AddDefaultAuthorizerToCorsPreflight,
auth_properties.Authorizers)

if auth_properties.ApiKeyRequired:
swagger_editor.add_apikey_security_definition()
Expand Down Expand Up @@ -586,7 +587,8 @@ def _get_authorizers(self, authorizers_config, default_authorizer=None):
function_arn=authorizer.get('FunctionArn'),
identity=authorizer.get('Identity'),
function_payload_type=authorizer.get('FunctionPayloadType'),
function_invoke_role=authorizer.get('FunctionInvokeRole')
function_invoke_role=authorizer.get('FunctionInvokeRole'),
authorization_scopes=authorizer.get("AuthorizationScopes")
)
return authorizers

Expand Down Expand Up @@ -636,7 +638,7 @@ def _construct_authorizer_lambda_permission(self):
return permissions

def _set_default_authorizer(self, swagger_editor, authorizers, default_authorizer,
add_default_auth_to_preflight=True):
add_default_auth_to_preflight=True, api_authorizers=None):
if not default_authorizer:
return

Expand All @@ -646,7 +648,8 @@ def _set_default_authorizer(self, swagger_editor, authorizers, default_authorize

for path in swagger_editor.iter_on_path():
swagger_editor.set_path_default_authorizer(path, default_authorizer, authorizers=authorizers,
add_default_auth_to_preflight=add_default_auth_to_preflight)
add_default_auth_to_preflight=add_default_auth_to_preflight,
api_authorizers=api_authorizers)

def _set_default_apikey_required(self, swagger_editor):
for path in swagger_editor.iter_on_path():
Expand Down
4 changes: 3 additions & 1 deletion samtranslator/model/apigateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ class ApiGatewayAuthorizer(object):
_VALID_FUNCTION_PAYLOAD_TYPES = [None, 'TOKEN', 'REQUEST']

def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_arn=None, identity=None,
function_payload_type=None, function_invoke_role=None, is_aws_iam_authorizer=False):
function_payload_type=None, function_invoke_role=None, is_aws_iam_authorizer=False,
authorization_scopes=[]):
if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES:
raise InvalidResourceException(api_logical_id, name + " Authorizer has invalid "
"'FunctionPayloadType': " + function_payload_type)
Expand All @@ -198,6 +199,7 @@ def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_
self.function_payload_type = function_payload_type
self.function_invoke_role = function_invoke_role
self.is_aws_iam_authorizer = is_aws_iam_authorizer
self.authorization_scopes = authorization_scopes

def _is_missing_identity_source(self, identity):
if not identity:
Expand Down
7 changes: 7 additions & 0 deletions samtranslator/model/eventsources/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,13 @@ def _add_swagger_integration(self, api, function):
'is only a valid value when a DefaultAuthorizer on the API is specified.'.format(
method=self.Method, path=self.Path))

if self.Auth.get("AuthorizationScopes") and not isinstance(self.Auth.get("AuthorizationScopes"), list):
raise InvalidEventException(
self.relative_id,
'Unable to set Authorizer on API method [{method}] for path [{path}] because '
'\'AuthorizationScopes\' must be a list of strings.'.format(method=self.Method,
path=self.Path))

apikey_required_setting = self.Auth.get('ApiKeyRequired')
apikey_required_setting_is_false = apikey_required_setting is not None and not apikey_required_setting
if apikey_required_setting_is_false and not api_auth.get('ApiKeyRequired'):
Expand Down
32 changes: 28 additions & 4 deletions samtranslator/swagger/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ def add_apikey_security_definition(self):
self.security_definitions.update(api_key_security_definition)

def set_path_default_authorizer(self, path, default_authorizer, authorizers,
add_default_auth_to_preflight=True):
add_default_auth_to_preflight=True, api_authorizers=None):
"""
Adds the default_authorizer to the security block for each method on this path unless an Authorizer
was defined at the Function/Path/Method level. This is intended to be used to set the
Expand Down Expand Up @@ -531,7 +531,8 @@ def set_path_default_authorizer(self, path, default_authorizer, authorizers,
# No existing Authorizer found; use default
else:
security_dict = {}
security_dict[default_authorizer] = []
security_dict[default_authorizer] = self._get_authorization_scopes(api_authorizers,
default_authorizer)
authorizer_security = [security_dict]

security = existing_non_authorizer_security + authorizer_security
Expand Down Expand Up @@ -622,14 +623,17 @@ def add_auth_to_method(self, path, method_name, auth, api):
:param dict api: Reference to the related Api's properties as defined in the template.
"""
method_authorizer = auth and auth.get('Authorizer')
method_scopes = auth and auth.get('AuthorizationScopes')
api_auth = api and api.get('Auth')
authorizers = api_auth and api_auth.get('Authorizers')
if method_authorizer:
self._set_method_authorizer(path, method_name, method_authorizer)
self._set_method_authorizer(path, method_name, method_authorizer, authorizers, method_scopes)

method_apikey_required = auth and auth.get('ApiKeyRequired')
if method_apikey_required is not None:
self._set_method_apikey_handling(path, method_name, method_apikey_required)

def _set_method_authorizer(self, path, method_name, authorizer_name):
def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers={}, method_scopes=None):
"""
Adds the authorizer_name to the security block for each method on this path.
This is used to configure the authorizer for individual functions.
Expand All @@ -656,6 +660,13 @@ def _set_method_authorizer(self, path, method_name, authorizer_name):
# This assumes there are no autorizers already configured in the existing security block
security = existing_security + authorizer_security

if authorizer_name != 'NONE' and authorizers:
method_auth_scopes = authorizers.get(authorizer_name, {}).get("AuthorizationScopes")
if method_scopes is not None:
method_auth_scopes = method_scopes
if authorizers.get(authorizer_name) is not None and method_auth_scopes is not None:
security_dict[authorizer_name] = method_auth_scopes

if security:
method_definition['security'] = security

Expand Down Expand Up @@ -1100,6 +1111,19 @@ def gen_skeleton():
}
}

@staticmethod
def _get_authorization_scopes(authorizers, default_authorizer):
"""
Returns auth scopes for an authorizer if present
:param authorizers: authorizer definitions
:param default_authorizer: name of the default authorizer
"""
if authorizers is not None:
if authorizers.get(default_authorizer) \
and authorizers[default_authorizer].get("AuthorizationScopes") is not None:
return authorizers[default_authorizer].get("AuthorizationScopes")
return []

@staticmethod
def _normalize_method_name(method):
"""
Expand Down
66 changes: 66 additions & 0 deletions tests/swagger/test_swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -1791,3 +1791,69 @@ def test_must_add_iam_allow_and_custom(self):
}

self.assertEqual(deep_sort_lists(expected), deep_sort_lists(self.editor.swagger[_X_POLICY]))

class TestSwaggerEditor_add_authorization_scopes(TestCase):
def setUp(self):
self.api = api = {
'Auth':{
'Authorizers': {
'MyOtherCognitoAuth':{},
'MyCognitoAuth': {}
},
'DefaultAuthorizer': "MyCognitoAuth"
}
}
self.editor = SwaggerEditor({
"swagger": "2.0",
"paths": {
"/cognito": {
"get": {
"x-amazon-apigateway-integration": {
"httpMethod": "POST",
"type": "aws_proxy",
"uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations"
}
},
"security": [],
"responses": {}
}
},
}
})

def test_should_include_auth_scopes_if_defined_with_authorizer(self):
auth = {
'AuthorizationScopes': ["ResourceName/method.scope"],
'Authorizer':"MyOtherCognitoAuth"
}
self.editor.add_auth_to_method("/cognito", "get", auth, self.api)
self.assertEqual([{"MyOtherCognitoAuth": ["ResourceName/method.scope"]}],
self.editor.swagger["paths"]["/cognito"]["get"]["security"])

def test_should_include_auth_scopes_with_default_authorizer(self):
auth = {
'AuthorizationScopes': ["ResourceName/method.scope"],
'Authorizer': 'MyCognitoAuth'
}
self.editor.add_auth_to_method("/cognito", "get", auth, self.api)
self.assertEqual([{"MyCognitoAuth": ["ResourceName/method.scope"]}],
self.editor.swagger["paths"]["/cognito"]["get"]["security"])

def test_should_include_only_specified_authorizer_auth_if_no_scopes_defined(self):
auth = {
'Authorizer':"MyOtherCognitoAuth"
}
self.editor.add_auth_to_method("/cognito", "get", auth, self.api)
self.assertEqual([{"MyOtherCognitoAuth": []}],
self.editor.swagger["paths"]["/cognito"]["get"]["security"])

def test_should_include_none_if_default_is_overwritte(self):
auth = {
'Authorizer':"NONE"
}

self.editor.add_auth_to_method("/cognito", "get", auth, self.api)
self.assertEqual([{"NONE": []}],
self.editor.swagger["paths"]["/cognito"]["get"]["security"])

88 changes: 88 additions & 0 deletions tests/translator/input/api_with_auth_with_default_scopes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Resources:
MyApiWithCognitoAuth:
Type: "AWS::Serverless::Api"
Properties:
StageName: Prod
Auth:
DefaultAuthorizer: MyDefaultCognitoAuth
Authorizers:
MyDefaultCognitoAuth:
UserPoolArn: arn:aws:1
AuthorizationScopes:
- default.write
- default.read
MyCognitoAuthWithDefaultScopes:
UserPoolArn: arn:aws:2
AuthorizationScopes:
- default.delete
- default.update

MyFn:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://bucket/key
Handler: index.handler
Runtime: nodejs8.10
Events:
CognitoAuthorizerWithDefaultScopes:
Type: Api
Properties:
RestApiId: !Ref MyApiWithCognitoAuth
Method: get
Path: /cognitoauthorizerwithdefaultscopes
Auth:
Authorizer: MyCognitoAuthWithDefaultScopes
CognitoDefaultScopesDefaultAuthorizer:
Type: Api
Properties:
RestApiId: !Ref MyApiWithCognitoAuth
Method: get
Path: /cognitodefaultscopesdefaultauthorizer
CognitoWithAuthNone:
Type: Api
Properties:
RestApiId: !Ref MyApiWithCognitoAuth
Method: get
Path: /cognitowithauthnone
Auth:
Authorizer: NONE
CognitoDefaultScopesWithOverwritten:
Type: Api
Properties:
RestApiId: !Ref MyApiWithCognitoAuth
Method: get
Path: /cognitodefaultscopesoverwritten
Auth:
Authorizer: MyDefaultCognitoAuth
AuthorizationScopes:
- overwritten.read
- overwritten.write
CognitoAuthorizerScopesOverwritten:
Type: Api
Properties:
RestApiId: !Ref MyApiWithCognitoAuth
Method: get
Path: /cognitoauthorizercopesoverwritten
Auth:
Authorizer: MyCognitoAuthWithDefaultScopes
AuthorizationScopes:
- overwritten.read
- overwritten.write
CognitoDefaultScopesNone:
Type: Api
Properties:
RestApiId: !Ref MyApiWithCognitoAuth
Method: get
Path: /cognitodefaultscopesnone
Auth:
Authorizer: MyDefaultCognitoAuth
AuthorizationScopes: []
CognitoDefaultAuythDefaultScopesNone:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Type: Api
Properties:
RestApiId: !Ref MyApiWithCognitoAuth
Method: get
Path: /cognitodefaultauthdefaultscopesnone
Auth:
Authorizer: MyCognitoAuthWithDefaultScopes
AuthorizationScopes: []
Loading