Пример #1
0
    def test_hmac_sha1_signature(self):
        self.prepare_data()
        handle = self.create_route()
        url = '/user'

        params = [
            ('oauth_consumer_key', 'client'),
            ('oauth_token', 'valid-token'),
            ('oauth_signature_method', 'HMAC-SHA1'),
            ('oauth_timestamp', str(int(time.time()))),
            ('oauth_nonce', 'hmac-sha1-nonce'),
        ]
        base_string = signature.construct_base_string(
            'GET', 'http://testserver/user', params
        )
        sig = signature.hmac_sha1_signature(
            base_string, 'secret', 'valid-token-secret')
        params.append(('oauth_signature', sig))
        auth_param = ','.join(['{}="{}"'.format(k, v) for k, v in params])
        auth_header = 'OAuth ' + auth_param

        # case 1: success
        request = self.factory.get(url, HTTP_AUTHORIZATION=auth_header)
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertIn('username', data)

        # case 2: exists nonce
        request = self.factory.get(url, HTTP_AUTHORIZATION=auth_header)
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertEqual(data['error'], 'invalid_nonce')
Пример #2
0
    def test_rsa_sha1_signature(self):
        self.prepare_data()
        handle = self.create_route()

        url = '/user'

        params = [
            ('oauth_consumer_key', 'client'),
            ('oauth_token', 'valid-token'),
            ('oauth_signature_method', 'RSA-SHA1'),
            ('oauth_timestamp', str(int(time.time()))),
            ('oauth_nonce', 'rsa-sha1-nonce'),
        ]
        base_string = signature.construct_base_string(
            'GET', 'http://testserver/user', params
        )
        sig = signature.rsa_sha1_signature(
            base_string, read_file_path('rsa_private.pem'))
        params.append(('oauth_signature', sig))
        auth_param = ','.join(['{}="{}"'.format(k, v) for k, v in params])
        auth_header = 'OAuth ' + auth_param

        request = self.factory.get(url, HTTP_AUTHORIZATION=auth_header)
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertIn('username', data)

        # case: invalid signature
        auth_param = auth_param.replace('rsa-sha1-nonce', 'alt-sha1-nonce')
        auth_header = 'OAuth ' + auth_param
        request = self.factory.get(url, HTTP_AUTHORIZATION=auth_header)
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertEqual(data['error'], 'invalid_signature')
Пример #3
0
def list_to_scope(scope):
    """Convert a list of scopes to a space separated string."""
    if isinstance(scope, (set, tuple, list)):
        return " ".join([to_unicode(s) for s in scope])
    if scope is None:
        return scope
    return to_unicode(scope)
Пример #4
0
 def _sign(h):
     protected, _, signature = self._sign_signature(
         h['protected'], payload, key, payload_segment)
     rv = {
         'protected': to_unicode(protected),
         'signature': to_unicode(signature)
     }
     if 'header' in header:
         rv['header'] = h['header']
     return rv
Пример #5
0
    def serialize_json(self, header_obj, payload, key):
        """Generate a JWS JSON Serialization. The JWS JSON Serialization
        represents digitally signed or MACed content as a JSON object,
        per `Section 7.2`_.

        :param header_obj: A dict/list of header
        :param payload: A string/dict of payload
        :param key: Private key used to generate signature
        :return: JWSObject

        Example ``header_obj`` of JWS JSON Serialization::

            {
                "protected: {"alg": "HS256"},
                "header": {"kid": "jose"}
            }

        Pass a dict to generate flattened JSON Serialization, pass a list of
        header dict to generate standard JSON Serialization.
        """
        payload_segment = json_b64encode(payload)

        def _sign(jws_header):
            self._validate_header(jws_header)
            _alg, _key = prepare_algorithm_key(self._algorithms,
                                               jws_header,
                                               payload,
                                               key,
                                               private=True)

            protected_segment = json_b64encode(jws_header.protected)
            signing_input = b'.'.join([protected_segment, payload_segment])
            signature = urlsafe_b64encode(_alg.sign(signing_input, _key))

            rv = {
                'protected': to_unicode(protected_segment),
                'signature': to_unicode(signature)
            }
            if jws_header.header is not None:
                rv['header'] = jws_header.header
            return rv

        if isinstance(header_obj, dict):
            data = _sign(JWSHeader.from_dict(header_obj))
            data['payload'] = to_unicode(payload_segment)
            return data

        signatures = [_sign(JWSHeader.from_dict(h)) for h in header_obj]
        return {
            'payload': to_unicode(payload_segment),
            'signatures': signatures
        }
Пример #6
0
    def test_invalid_request_parameters(self):
        self.prepare_data()
        handle = self.create_route()
        url = '/user'

        # case 1
        request = self.factory.get(url)
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertEqual(data['error'], 'missing_required_parameter')
        self.assertIn('oauth_consumer_key', data['error_description'])

        # case 2
        request = self.factory.get(
            add_params_to_uri(url, {'oauth_consumer_key': 'a'}))
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertEqual(data['error'], 'invalid_client')

        # case 3
        request = self.factory.get(
            add_params_to_uri(url, {'oauth_consumer_key': 'client'}))
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertEqual(data['error'], 'missing_required_parameter')
        self.assertIn('oauth_token', data['error_description'])

        # case 4
        request = self.factory.get(
            add_params_to_uri(url, {
                'oauth_consumer_key': 'client',
                'oauth_token': 'a'
            })
        )
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertEqual(data['error'], 'invalid_token')

        # case 5
        request = self.factory.get(
            add_params_to_uri(url, {
                'oauth_consumer_key': 'client',
                'oauth_token': 'valid-token'
            })
        )
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertEqual(data['error'], 'missing_required_parameter')
        self.assertIn('oauth_timestamp', data['error_description'])
Пример #7
0
        def _sign(jws_header):
            self._validate_private_headers(jws_header)
            _alg, _key = self._prepare_algorithm_key(jws_header, payload, key)

            protected_segment = json_b64encode(jws_header.protected)
            signing_input = b'.'.join([protected_segment, payload_segment])
            signature = urlsafe_b64encode(_alg.sign(signing_input, _key))

            rv = {
                'protected': to_unicode(protected_segment),
                'signature': to_unicode(signature)
            }
            if jws_header.header is not None:
                rv['header'] = jws_header.header
            return rv
Пример #8
0
def generate_api_key(kid, private_key, user_id, expires_in, scopes, client_id):
    """
    Generate a JWT refresh token and output a UTF-8
    string of the encoded JWT signed with the private key.

    Args:
        kid (str): key id of the keypair used to generate token
        private_key (str): RSA private key to sign and encode the JWT with
        user_id (user id): User id to generate token for
        expires_in (int): seconds until expiration
        scopes (List[str]): oauth scopes for user_id

    Return:
        str: encoded JWT refresh token signed with ``private_key``
    """
    headers = {"kid": kid}
    iat, exp = issued_and_expiration_times(expires_in)
    jti = str(uuid.uuid4())
    sub = str(user_id)
    claims = {
        "pur": "api_key",
        "aud": scopes,
        "sub": sub,
        "iss": config.get("BASE_URL"),
        "iat": iat,
        "exp": exp,
        "jti": jti,
        "azp": client_id or "",
    }
    logger.info("issuing JWT API key with id [{}] to [{}]".format(jti, sub))
    logger.debug("issuing JWT API key\n" + json.dumps(claims, indent=4))
    token = jwt.encode(claims, private_key, headers=headers, algorithm="RS256")
    logger.debug(str(token))
    token = to_unicode(token, "UTF-8")
    return JWTResult(token=token, kid=kid, claims=claims)
Пример #9
0
 def dumps_private_key(self):
     obj = self.dumps_public_key(self.private_key.public_key())
     d_bytes = self.private_key.private_bytes(Encoding.Raw,
                                              PrivateFormat.Raw,
                                              NoEncryption())
     obj['d'] = to_unicode(urlsafe_b64encode(d_bytes))
     return obj
Пример #10
0
    def init_jwt_config(self, app):
        """Initialize JWT related configuration."""
        jwt_iss = app.config.get('OAUTH2_JWT_ISS')
        if not jwt_iss:
            raise RuntimeError('Missing "OAUTH2_JWT_ISS" configuration.')
        jwt_key_path = app.config.get('OAUTH2_JWT_KEY_PATH')
        if jwt_key_path:
            with open(jwt_key_path, 'r') as f:
                if jwt_key_path.endswith('.json'):
                    jwt_key = json.load(f)
                else:
                    jwt_key = to_unicode(f.read())
        else:
            jwt_key = app.config.get('OAUTH2_JWT_KEY')

        if not jwt_key:
            raise RuntimeError('Missing "OAUTH2_JWT_KEY" configuration.')

        jwt_alg = app.config.get('OAUTH2_JWT_ALG')
        if not jwt_alg:
            raise RuntimeError('Missing "OAUTH2_JWT_ALG" configuration.')

        jwt_exp = app.config.get('OAUTH2_JWT_EXP', 3600)
        self.config.setdefault('jwt_iss', jwt_iss)
        self.config.setdefault('jwt_key', jwt_key)
        self.config.setdefault('jwt_alg', jwt_alg)
        self.config.setdefault('jwt_exp', jwt_exp)
Пример #11
0
def scope_to_list(scope):
    """Convert a space separated string to a list of scopes."""
    if isinstance(scope, (tuple, list, set)):
        return [to_unicode(s) for s in scope]
    elif scope is None:
        return None
    return scope.strip().split()
Пример #12
0
    def decode(self,
               s,
               key,
               claims_cls=None,
               claims_options=None,
               claims_params=None):
        """Decode the JWS with the given key. This is similar with
        :meth:`verify`, except that it will raise BadSignatureError when
        signature doesn't match.

        :param s: text of JWT
        :param key: key used to verify the signature
        :param claims_cls: class to be used for JWT claims
        :param claims_options: `options` parameters for claims_cls
        :param claims_params: `params` parameters for claims_cls
        :return: claims_cls instance
        :raise: BadSignatureError
        """
        if claims_cls is None:
            claims_cls = JWTClaims
        header, bytes_payload = super(JWT, self).decode(s, key)
        payload = json.loads(to_unicode(bytes_payload))
        return claims_cls(
            payload,
            header,
            options=claims_options,
            params=claims_params,
        )
Пример #13
0
    def create_oauth2_request_from_json(self, request):
        """Build the OAuth2Request object from the JSON request body.

        Args:
            request (obj): The request object to parse for the necessary elements to build the OAuth2Request.

        Returns:
            (obj): The OAuth2Request object.

        """

        request_cls = OAuth2Request

        if isinstance(request, request_cls):
            return request

        if not request:
            request = flask_req

        # in case we cannot determine if the header is json, we hand the workload off to the base method.
        try:
            if request.headers['Content-Type'] != 'application/json':
                return self.create_authorization_request(request)
        except Exception:
            return self.create_oauth2_request(request)

        if request.method == 'POST':
            body = request.json
        else:
            body = None

        url = request.base_url
        if request.query_string:
            url = url + '?' + to_unicode(request.query_string)
        return request_cls(request.method, url, body, request.headers)
Пример #14
0
    def generate_id_token(self,
                          token,
                          request,
                          nonce=None,
                          auth_time=None,
                          code=None):

        scopes = scope_to_list(token['scope'])
        if not scopes or scopes[0] != 'openid':
            return None

        # TODO: merge scopes and claims
        user_info = self.generate_user_info(request.user, scopes)

        now = int(time.time())
        if auth_time is None:
            auth_time = now

        config = self.server.config
        payload = {
            'iss': config['jwt_iss'],
            'aud': [request.client.client_id],
            'iat': now,
            'exp': now + token['expires_in'],
            'auth_time': auth_time,
        }
        if nonce:
            payload['nonce'] = nonce

        # calculate at_hash
        alg = config.get('jwt_alg', 'HS256')

        access_token = token.get('access_token')
        if access_token:
            at_hash = to_unicode(create_half_hash(access_token, alg))
            payload['at_hash'] = at_hash

        # calculate c_hash
        if code:
            payload['c_hash'] = to_unicode(create_half_hash(code, alg))

        payload.update(user_info)
        jwt = JWT(algorithms=alg)
        header = {'alg': alg}
        key = config['jwt_key']
        id_token = jwt.encode(header, payload, key)
        return to_unicode(id_token)
Пример #15
0
 def dumps_public_key(self, public_key=None):
     if public_key is None:
         public_key = self.public_key
     x_bytes = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
     return {
         'crv': self.get_key_curve(public_key),
         'x': to_unicode(urlsafe_b64encode(x_bytes)),
     }
Пример #16
0
def decode_payload(bytes_payload):
    try:
        payload = json.loads(to_unicode(bytes_payload))
    except ValueError:
        raise DecodeError('Invalid payload value')
    if not isinstance(payload, dict):
        raise DecodeError('Invalid payload type')
    return payload
Пример #17
0
def extract_basic_authorization(token):
    """Extract token from Basic Authorization."""
    try:
        query = to_unicode(base64.b64decode(token))
    except TypeError:
        return None, None
    if ':' in query:
        return query.split(':', 1)
    return query, None
Пример #18
0
    def config_app(self):
        jwt_key_path = get_file_path('rsa_private.pem')
        with open(jwt_key_path, 'r') as f:
            jwt_key = to_unicode(f.read())

        DUMMY_JWT_CONFIG.update({
            'iss': 'Authlib',
            'key': jwt_key,
            'alg': 'RS256',
        })
Пример #19
0
def _create_oauth1_request():
    if _req.method == 'POST':
        body = _req.form.to_dict(flat=True)
    else:
        body = None

    url = _req.base_url
    if _req.query_string:
        url = url + '?' + to_unicode(_req.query_string)
    return OAuth1Request(_req.method, url, body, _req.headers)
Пример #20
0
    def serialize_json(self, header, payload, key):
        """Generate a JWS JSON Serialization. The JWS JSON Serialization
        represents digitally signed or MACed content as a JSON object,
        per `Section 7.2`_.

        :param header: A dict/list of header
        :param payload: A string/dict of payload
        :param key: Private key used to generate signature
        :return: dict

        Example header of JWS JSON Serialization::

            {
                "protected: {"alg": "HS256"},
                "header": {"kid": "jose"}
            }

        Pass a dict to generate flattened JSON Serialization, pass a list of
        header dict to generate standard JSON Serialization.
        """
        payload_segment = _b64encode_json(payload)

        def _sign(h):
            protected, _, signature = self._sign_signature(
                h['protected'], payload, key, payload_segment)
            rv = {
                'protected': to_unicode(protected),
                'signature': to_unicode(signature)
            }
            if 'header' in header:
                rv['header'] = h['header']
            return rv

        if isinstance(header, dict):
            data = _sign(header)
            data['payload'] = to_unicode(payload_segment)
            return data

        signatures = [_sign(h) for h in header]
        return {
            'payload': to_unicode(payload_segment),
            'signatures': signatures
        }
Пример #21
0
def create_basic_header(username, password):
    """
    Create an authorization header from the username and password according to
    RFC 2617 (https://tools.ietf.org/html/rfc2617).

    Use this to send client credentials in the authorization header.
    """
    text = "{}:{}".format(username, password)
    auth = to_unicode(base64.b64encode(to_bytes(text)))
    return {"Authorization": "Basic " + auth}
Пример #22
0
 def extract_payload(self, payload_segment):
     """Extract payload into JSON dict format."""
     bytes_payload = super(JWT, self).extract_payload(payload_segment)
     try:
         payload = json.loads(to_unicode(bytes_payload))
     except ValueError:
         raise DecodeError('Invalid payload value')
     if not isinstance(payload, dict):
         raise DecodeError('Invalid payload type')
     return payload
Пример #23
0
def _ensure_dict(s):
    if not isinstance(s, dict):
        try:
            s = json.loads(to_unicode(s))
        except (ValueError, TypeError):
            raise DecodeError('Invalid JWS')

    if not isinstance(s, dict):
        raise DecodeError('Invalid JWS')

    return s
Пример #24
0
def generate_signed_refresh_token(kid,
                                  private_key,
                                  user,
                                  expires_in,
                                  scopes,
                                  iss=None,
                                  client_id=None):
    """
    Generate a JWT refresh token and output a UTF-8
    string of the encoded JWT signed with the private key.

    Args:
        kid (str): key id of the keypair used to generate token
        private_key (str): RSA private key to sign and encode the JWT with
        user (fence.models.User): User to generate token for
        expires_in (int): seconds until expiration
        scopes (List[str]): oauth scopes for user

    Return:
        str: encoded JWT refresh token signed with ``private_key``
    """
    headers = {"kid": kid}
    iat, exp = issued_and_expiration_times(expires_in)
    jti = str(uuid.uuid4())
    sub = str(user.id)
    if not iss:
        try:
            iss = config.get("BASE_URL")
        except RuntimeError:
            raise ValueError("must provide value for `iss` (issuer) field if"
                             " running outside of flask application")
    claims = {
        "pur": "refresh",
        "sub": sub,
        "iss": iss,
        "aud": [iss],
        "iat": iat,
        "exp": exp,
        "jti": jti,
        "azp": client_id or "",
        "scope": scopes,
    }

    if client_id:
        claims["aud"].append(client_id)

    logger.info("issuing JWT refresh token with id [{}] to [{}]".format(
        jti, sub))
    logger.debug(f"issuing JWT refresh token: {claims}")

    token = jwt.encode(claims, private_key, headers=headers, algorithm="RS256")
    token = to_unicode(token, "UTF-8")

    return JWTResult(token=token, kid=kid, claims=claims)
Пример #25
0
def ensure_dict(s, structure_name):
    if not isinstance(s, dict):
        try:
            s = json_loads(to_unicode(s))
        except (ValueError, TypeError):
            raise DecodeError('Invalid {}'.format(structure_name))

    if not isinstance(s, dict):
        raise DecodeError('Invalid {}'.format(structure_name))

    return s
Пример #26
0
    def test_plaintext_signature(self):
        self.prepare_data()
        handle = self.create_route()
        url = '/user'

        # case 1: success
        auth_header = ('OAuth oauth_consumer_key="client",'
                       'oauth_signature_method="PLAINTEXT",'
                       'oauth_token="valid-token",'
                       'oauth_signature="secret&valid-token-secret"')
        request = self.factory.get(url, HTTP_AUTHORIZATION=auth_header)
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertIn('username', data)

        # case 2: invalid signature
        auth_header = auth_header.replace('valid-token-secret', 'invalid')
        request = self.factory.get(url, HTTP_AUTHORIZATION=auth_header)
        resp = handle(request)
        data = json.loads(to_unicode(resp.content))
        self.assertEqual(data['error'], 'invalid_signature')
Пример #27
0
def prepare_grant_uri(uri,
                      client_id,
                      response_type,
                      redirect_uri=None,
                      scope=None,
                      state=None,
                      **kwargs):
    """Prepare the authorization grant request URI.

    The client constructs the request URI by adding the following
    parameters to the query component of the authorization endpoint URI
    using the ``application/x-www-form-urlencoded`` format:

    :param uri: The authorize endpoint to fetch "code" or "token".
    :param client_id: The client identifier as described in `Section 2.2`_.
    :param response_type: To indicate which OAuth 2 grant/flow is required,
                          "code" and "token".
    :param redirect_uri: The client provided URI to redirect back to after
                         authorization as described in `Section 3.1.2`_.
    :param scope: The scope of the access request as described by
                  `Section 3.3`_.
    :param state: An opaque value used by the client to maintain
                  state between the request and callback.  The authorization
                  server includes this value when redirecting the user-agent
                  back to the client.  The parameter SHOULD be used for
                  preventing cross-site request forgery as described in
                  `Section 10.12`_.
    :param kwargs: Extra arguments to embed in the grant/authorization URL.

    An example of an authorization code grant authorization URL::

        /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

    .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
    .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
    .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
    .. _`section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
    """
    params = [('response_type', response_type), ('client_id', client_id)]

    if redirect_uri:
        params.append(('redirect_uri', redirect_uri))
    if scope:
        params.append(('scope', list_to_scope(scope)))
    if state:
        params.append(('state', state))

    for k in kwargs:
        if kwargs[k]:
            params.append((to_unicode(k), kwargs[k]))

    return add_params_to_uri(uri, params)
Пример #28
0
    def thumbprint(self):
        """Implementation of RFC7638 JSON Web Key (JWK) Thumbprint."""
        fields = list(self.REQUIRED_JSON_FIELDS)
        fields.append('kty')
        fields.sort()
        data = OrderedDict()

        for k in fields:
            data[k] = self.tokens[k]

        json_data = json_dumps(data)
        digest_data = hashlib.sha256(to_bytes(json_data)).digest()
        return to_unicode(urlsafe_b64encode(digest_data))
Пример #29
0
def generate_signed_access_token(
        kid, private_key, user, expires_in, scopes, forced_exp_time=None):
    """
    Generate a JWT access token and output a UTF-8
    string of the encoded JWT signed with the private key.

    Args:
        kid (str): key id of the keypair used to generate token
        private_key (str): RSA private key to sign and encode the JWT with
        user (fence.models.User): User to generate ID token for
        expires_in (int): seconds until expiration
        scopes (List[str]): oauth scopes for user

    Return:
        str: encoded JWT access token signed with ``private_key``
    """
    headers = {'kid': kid}

    iat, exp = issued_and_expiration_times(expires_in)

    # force exp time if provided
    exp = forced_exp_time or exp
    sub = str(user.id)
    jti = str(uuid.uuid4())
    claims = {
        'pur': 'access',
        'aud': scopes,
        'sub': sub,
        'iss': flask.current_app.config.get('BASE_URL'),
        'iat': iat,
        'exp': exp,
        'jti': jti,
        'context': {
            'user': {
                'name': user.username,
                'is_admin': user.is_admin,
                'projects': dict(user.project_access),
            },
        },
    }
    flask.current_app.logger.info(
        'issuing JWT access token with id [{}] to [{}]'.format(jti, sub)
    )
    flask.current_app.logger.debug(
        'issuing JWT access token\n' + json.dumps(claims, indent=4)
    )
    token = jwt.encode(claims, private_key, headers=headers, algorithm='RS256')
    flask.current_app.logger.debug(str(token))
    token = to_unicode(token, 'UTF-8')
    return token
Пример #30
0
 def setQueryArguments(self, **kwargs):
     """Set query arguments"""
     for k in kwargs:
         # Quote value before add it to request query
         value = (
             "+".join([quote(str(v)) for v in kwargs[k]]) if isinstance(kwargs[k], list) else quote(str(kwargs[k]))
         )
         # Remove argument from uri
         query = re.sub(r"&{argument}(=[^&]*)?|^{argument}(=[^&]*)?&?".format(argument=k), "", self.query)
         # Add new one
         if query:
             query += "&"
         query += "%s=%s" % (k, value)
     # Re-init class
     self.__init__(self.method, to_unicode(self.path + "?" + query))