Пример #1
0
    def validate_access_token_request(self):
        """
        Override the parent method from authlib to not fail immediately for
        public clients.
        """
        client = self.authenticate_token_endpoint_client()
        if not client.check_grant_type(self.GRANT_TYPE):
            raise UnauthorizedClientError(uri=self.uri)
        self._authenticated_client = client

        refresh_token = self.params.get("refresh_token")
        if refresh_token is None:
            raise InvalidRequestError(
                'Missing "refresh_token" in request.', uri=self.uri
            )

        refresh_claims = self.authenticate_refresh_token(refresh_token)
        if not refresh_claims:
            raise InvalidRequestError(
                'Invalid "refresh_token" in request.', uri=self.uri
            )

        scope = self.params.get("scope")
        if scope:
            original_scope = refresh_claims["scope"]
            if not original_scope:
                raise InvalidScopeError(uri=self.uri)
            original_scope = set(scope_to_list(original_scope))
            if not original_scope.issuperset(set(scope_to_list(scope))):
                raise InvalidScopeError(uri=self.uri)

        self._authenticated_token = refresh_claims
Пример #2
0
    def _validate_token_scope(self, token):
        """
        OVERRIDES method from authlib.

        Why? Becuase our "token" is not a class with `get_scope` method.
        So we just need to treat it like a dictionary.
        """
        scope = self.request.scope
        if not scope:
            return

        # token is dict so just get the scope, don't use get_scope()
        original_scope = token.get("scope")

        ##### begin refresh token patch block #####
        # TODO: In the next release, remove this if block
        # Old refresh tokens are not compatible with new validation, so to smooth
        # the transition, allow old style refresh tokens with this patch;
        # remove patch in next tag. Refresh tokens have default TTL of 30 days.
        if not original_scope:
            original_scope = token.get("aud")
        ##### end refresh token patch block #####

        if not original_scope:
            raise InvalidScopeError(
                "No scope claim found in original refresh token.")

        original_scope = set(scope_to_list(original_scope))
        if not original_scope.issuperset(set(scope_to_list(scope))):
            raise InvalidScopeError(
                "Cannot request scopes that were not in original refresh token."
            )
Пример #3
0
    def scope_insufficient(self, token: AzureToken, scope: str, operator: Union[str, Callable] = 'AND') -> bool:
        """
        Determines whether a token has sufficient scopes to interact with a resource

        I.e. whether the token bearer has suitable permissions to perform their intended action.

        This method overloads the default method in the 'BearerTokenValidator' class to make it compatible with our
        AzureToken class.

        :type token: AzureToken
        :param token: JSON Web Token as an Azure Token object
        :type scope: str
        :param scope: space concatenated list of scopes required to interact with the current resource
        :type operator: str or Callable
        :param operator: Strategy of validating whether token scopes meet resource scopes (i.e. all represent, at
        least one present)

        :rtype bool
        :return: True if the token has insufficient scopes, False if ok
        """
        if not scope:
            return False

        token_scopes = token.scopes
        resource_scopes = set(scope_to_list(scope))

        if operator == 'AND':
            return not token_scopes.issuperset(resource_scopes)
        if operator == 'OR':
            for resource_scope in resource_scopes:
                if resource_scope in token_scopes:
                    return False
        if callable(operator):
            return not operator(token_scopes, resource_scopes)
        raise ValueError(f"Invalid operator value [{ operator }], valid options are 'AND', 'OR' or <callable>")
Пример #4
0
def generate_id_token(key,
                      token,
                      request,
                      alg,
                      iss,
                      exp,
                      nonce=None,
                      auth_time=None,
                      code=None):
    scopes = scope_to_list(token['scope'])

    # TODO: merge scopes and claims
    user_info = _generate_user_info(request.user, scopes)
    client = request.client

    payload = _generate_id_token_payload(
        alg,
        iss,
        [client.get_client_id()],
        exp=exp,
        nonce=nonce,
        auth_time=auth_time,
        code=code,
        access_token=token.get('access_token'),
    )
    payload.update(user_info)
    return _jwt_encode(alg, payload, key)
Пример #5
0
    def get_allowed_scope(self, scope):
        """Get allowed scope. Has been slightly modified to accommodate parametric scopes.

        :param str scope: requested scope

        :return: str -- scopes
        """
        if not isinstance(scope, six.string_types):
            scope = list_to_scope(scope)
        allowed = scope_to_list(super(Client, self).get_allowed_scope(scope))
        for s in scope_to_list(scope):
            for def_scope in scope_to_list(self.scope):
                if s.startswith(def_scope) and s not in allowed:
                    allowed.append(s)
        gLogger.debug('Try to allow "%s" scope:' % scope, allowed)
        return list_to_scope(list(set(allowed)))
Пример #6
0
    def getGroupScopes(self, group: str) -> list:
        """Get group scopes

        :param group: DIRAC group
        """
        idPScope = getGroupOption(group, "IdPRole")
        return scope_to_list(idPScope) if idPScope else []
Пример #7
0
    def group(self):
        """Search DIRAC group in scopes

        :return: str
        """
        groups = [s.split(":")[1] for s in scope_to_list(self.scope or "") if s.startswith("g:") and s.split(":")[1]]
        return groups[0] if groups else None
Пример #8
0
 async def async_generate_user_info(self, user: UserWithRoles, scope: str):
     scope_list = scope_to_list(scope)
     includes = set()
     for scope in scope_list:
         if scope not in ('openid', 'offline_access'):
             includes.update(config.oauth2.user.scopes[scope].properties)
     user_data = user.user.dict(include=includes,
                                by_alias=True,
                                exclude_none=True)
     user_data['sub'] = user.user.id
     user_data['roles'] = user.roles
     if 'picture' in user_data:
         user_data[
             'picture'] = f"{config.oauth2.base_url}/picture/{user_data['picture']}"
     if 'groups' in user_data:
         # Only include visible groups
         user_data['groups'] = [
             group['_id']
             async for group in async_user_group_collection.find(
                 {
                     '_id': {
                         '$in': user_data['groups']
                     },
                     'visible': True
                 },
                 projection={'_id': 1})
         ]
     return UserInfo(**user_data)
Пример #9
0
 def validate_requested_scope(self, scope, state=None):
     """See :func:`authlib.oauth2.rfc6749.authorization_server.validate_requested_scope`"""
     # We also consider parametric scope containing ":" charter
     extended_scope = list_to_scope(
         [re.sub(r":.*$", ":", s) for s in scope_to_list((scope or "").replace("+", " "))]
     )
     super(AuthServer, self).validate_requested_scope(extended_scope, state)
Пример #10
0
    def get_allowed_scope(self, scope: str) -> str:
        """Returns the allowed scope."""
        if not scope:
            return ''

        allowed = {scope.scope for scope in self.scopes}
        scopes = scope_to_list(scope)
        return list_to_scope([scope for scope in scopes if scope in allowed])
Пример #11
0
    def _validate_token_scope(self, token):
        """
        OVERRIDES method from authlib.

        Why? Becuase our "token" is not a class with `get_scope` method.
        So we just need to treat it like a dictionary.
        """
        scope = self.request.scope
        if not scope:
            return

        # token is dict so just get the scope, don't use get_scope()
        original_scope = token.get("aud")
        if not original_scope:
            raise InvalidScopeError()

        original_scope = set(scope_to_list(original_scope))
        if not original_scope.issuperset(set(scope_to_list(scope))):
            raise InvalidScopeError()
Пример #12
0
    def process_implicit_token(self, token, code=None):
        config = self.get_jwt_config()
        config['nonce'] = self.request.data.get('nonce')
        if code is not None:
            config['code'] = code

        scopes = scope_to_list(token['scope'])
        user_info = self.generate_user_info(self.request.user, scopes)

        id_token = generate_id_token(token, self.request, user_info, **config)
        token['id_token'] = id_token
        return token
Пример #13
0
 def __call__(self,
              client,
              grant_type,
              user=None,
              scope=None,
              expires_in=None,
              include_refresh_token=True):
     if 'offline_access' not in scope_to_list(scope):
         include_refresh_token = False
     return super(BearerToken,
                  self).__call__(client, grant_type, user, scope,
                                 expires_in, include_refresh_token)
Пример #14
0
    def getGroupScopes(self, group):
        """Get group scopes

        :param str group: DIRAC group

        :return: list
        """
        idPScope = getGroupOption(group, "IdPRole")
        if not idPScope:
            idPScope = "wlcg.groups:/%s/%s" % (getVOForGroup(group),
                                               group.split("_")[1])
        return S_OK(scope_to_list(idPScope))
Пример #15
0
    def _getScope(self, scope, param):
        """Get parameter scope

        :param str scope: scope
        :param str param: parameter scope

        :return: str or None
        """
        try:
            return [s.split(":")[1] for s in scope_to_list(scope) if s.startswith("%s:" % param) and s.split(":")[1]][0]
        except Exception:
            return None
Пример #16
0
    def exchangeToken(self, group=None, scope=None):
        """Get new tokens for group scope

        :param str group: requested group
        :param list scope: requested scope

        :return: dict -- token
        """
        scope = scope or scope_to_list(self.scope)
        if group:
            if not (groupScopes := self.getGroupScopes(group)):
                return S_ERROR(f"No scope found for {group}")
            scope = list(set(scope + groupScopes))
Пример #17
0
    def getGroupScopes(self, group):
        """Get group scopes

        :param str group: DIRAC group

        :return: list
        """
        idPScope = getGroupOption(group, "IdPRole")
        if not idPScope:
            idPScope = "eduperson_entitlement?value=urn:mace:egi.eu:group:%s:role=%s#aai.egi.eu" % (
                getVOForGroup(group),
                group.split("_")[1],
            )
        return S_OK(scope_to_list(idPScope))
Пример #18
0
    def __call__(self, client: Client, grant_type: str, user: UserWithRoles,
                 scope: str):
        jwt_config = self.get_jwt_config()
        jwt_config['aud'] = [client.get_client_id()]
        jwt_config['auth_time'] = int(time.time())

        user_info = {'sub': user.user.id, 'roles': user.roles}
        if 'groups' in scope_to_list(scope):
            user_info['groups'] = user.user.groups
        return generate_id_token({},
                                 user_info,
                                 code=generate_token(
                                     config.oauth2.access_token_length),
                                 **jwt_config)
Пример #19
0
    def process_token(self, grant, token):
        scope = token.get('scope')
        if not scope or not is_openid_scope(scope):
            # standard authorization code flow
            return token

        request = grant.request
        credential = request.credential

        scopes = scope_to_list(token['scope'])
        user_info = self.generate_user_info(request.user, scopes)

        config = self.get_jwt_config(grant)
        config['nonce'] = credential.get_nonce()
        config['auth_time'] = credential.get_auth_time()
        id_token = generate_id_token(token, request, user_info, **config)
        token['id_token'] = id_token
        return token
Пример #20
0
 async def create_response(self, request: TypedRequest,
                           user_id: str) -> Response:
     try:
         assert isinstance(request, OAuth2Request)
         request.token = await run_in_threadpool(
             resource_protector.validate_request, None, request)
         if request.token is None:
             raise HTTPException(403, "Invalid token")
         if 'users' not in scope_to_list(request.token.scope):
             raise InsufficientScopeError('Missing "users" scope',
                                          request.uri)
         user = await UserWithRoles.async_load(user_id,
                                               request.token.client_id)
         if user is None:
             raise HTTPException(404, "User not found")
         user_info = await self.async_generate_user_info(user, 'users')
         return JSONResponse(user_info)
     except OAuth2Error as error:
         return authorization.handle_error_response(request, error)
Пример #21
0
    def generate_user_info(self, user, scope):  # pragma: no cover
        """Provide user information for the given scope. Developers
        MUST implement this method in subclass, e.g.::

            from authlib.oidc.core import UserInfo

            def generate_user_info(self, user, scope):
                user_info = UserInfo(sub=user.id, name=user.name)
                if 'email' in scope:
                    user_info['email'] = user.email
                return user_info

        :param user: user instance
        :param scope: scope of the token
        :return: ``authlib.oidc.core.UserInfo`` instance
        """
        deprecate('Missing "OpenIDCode.generate_user_info"', '1.0', 'fjPsV',
                  'oi')
        scopes = scope_to_list(scope)
        return _generate_user_info(user, scopes)
Пример #22
0
 async def create_response(self, request: TypedRequest) -> Response:
     try:
         assert isinstance(request, OAuth2Request)
         request.token = await run_in_threadpool(
             resource_protector.validate_request, None, request)
         if request.token is None:
             raise HTTPException(403, "Invalid token")
         if 'users' not in scope_to_list(request.token.scope):
             raise InsufficientScopeError('Missing "users" scope',
                                          request.uri)
         user_infos = []
         async for user in UserWithRoles.async_load_all(
                 request.token.client_id):
             user_info = await self.async_generate_user_info(
                 UserWithRoles(user=user,
                               roles=[],
                               last_modified=user.updated_at), 'users')
             del user_info['roles']
             user_infos.append(user_info)
         return JSONResponse(user_infos)
     except OAuth2Error as error:
         return authorization.handle_error_response(request, error)
Пример #23
0
    def groups(self):
        """Search DIRAC groups in scopes

        :return: list
        """
        return [s.split(":")[1] for s in scope_to_list(self.scope or "") if s.startswith("g:") and s.split(":")[1]]
Пример #24
0
def is_openid_scope(scope):
    scopes = scope_to_list(scope)
    return scopes and scopes[0] == 'openid'
Пример #25
0
 def test_scope_to_list(self):
     self.assertEqual(util.scope_to_list('a b'), ['a', 'b'])
     self.assertEqual(util.scope_to_list(['a', 'b']), ['a', 'b'])
     self.assertIsNone(util.scope_to_list(None))
Пример #26
0
 def get_allowed_scope(self, scope):
     if not scope:
         return ''
     allowed = set(self.scope.split())
     scopes = scope_to_list(scope)
     return list_to_scope([s for s in scopes if s in allowed])
Пример #27
0
def is_openid_scope(scope):
    scopes = scope_to_list(scope)
    return scopes and 'openid' in scopes
Пример #28
0
        return S_OK((credDict, payload))

    def submitDeviceCodeAuthorizationFlow(self, group=None):
        """Submit authorization flow

        :return: S_OK(dict)/S_ERROR() -- dictionary with device code flow response
        """
        groupScopes = []
        if group:
            if not (groupScopes := self.getGroupScopes(group)):
                return S_ERROR(f"No scope found for {group}")

        try:
            r = requests.post(
                self.get_metadata("device_authorization_endpoint"),
                data=dict(client_id=self.client_id, scope=list_to_scope(scope_to_list(self.scope) + groupScopes)),
                verify=self.verify,
            )
            r.raise_for_status()
            deviceResponse = r.json()
            if "error" in deviceResponse:
                return S_ERROR("%s: %s" % (deviceResponse["error"], deviceResponse.get("description", "")))

            # Check if all main keys are present here
            for k in ["user_code", "device_code", "verification_uri"]:
                if not deviceResponse.get(k):
                    return S_ERROR("Mandatory %s key is absent in authentication response." % k)

            return S_OK(deviceResponse)
        except requests.exceptions.Timeout:
            return S_ERROR("Authentication server is not answer, timeout.")
Пример #29
0
 def getScopeGroups(self, scope: str) -> list:
     """Get DIRAC groups related to scope"""
     groups = []
     for group in getAllGroups():
         if (g_scope := self.getGroupScopes(group)) and set(g_scope).issubset(scope_to_list(scope)):
             groups.append(group)
Пример #30
0
    def addScopes(self, scopes):
        """Add new scopes to query

        :param list scopes: scopes
        """
        self.setQueryArguments(scope=list(set(scope_to_list(self.scope or "") + scopes)))