Example #1
0
def cancel_session_token(request, context):
    authenticated_user = authenticate_user(request, context)
    utils.validate_not_empty(request, 'session_token')

    # Retrieve the session details from the database
    try:
        session = get_session_from_token(request.session_token)
    except DatabaseObjectNotFound:
        abort(grpc.StatusCode.PERMISSION_DENIED,
              'Session token is invalid or has expired')

    # Verify that the caller is the authorized renewer for the token
    # (The renewer can also cancel the token)
    if session.renewer != authenticated_user:
        abort(grpc.StatusCode.PERMISSION_DENIED,
              f'Unauthorized renewer: {authenticated_user}')

    # Cancel the token
    session.delete()

    # Create and return RPC response
    response = broker_pb2.CancelSessionTokenResponse()
    return response, {
        'owner': session.owner,
        'renewer': session.renewer,
        'session-id': session.id
    }
Example #2
0
def validate_scope(scope):
    # Check if the given scope is whitelisted
    scope_set = set(scope.split(','))
    whitelist = set(settings.SCOPE_WHITELIST.split(','))
    if not scope_set.issubset(whitelist):
        abort(grpc.StatusCode.PERMISSION_DENIED,
              f'`{scope}` is not a whitelisted scope')
Example #3
0
    def get_access_token(self, owner, scope):
        google_identity = self.get_google_identity(owner)

        try:
            refresh_token = RefreshToken.get(google_identity)
        except DatabaseObjectNotFound:
            # The user has not authorized the broker yet
            abort(grpc.StatusCode.PERMISSION_DENIED,
                  self.AUTHZ_ERROR_MESSAGE.format(owner))

        oauthsession, client_config = session_from_client_secrets_file(
            settings.CLIENT_SECRET_PATH, scopes=scope.split(','))
        decrypted_value = encryption.decrypt(
            settings.ENCRYPTION_REFRESH_TOKEN_CRYPTO_KEY, refresh_token.value)

        try:
            access_token = oauthsession.refresh_token(
                token_url='https://oauth2.googleapis.com/token',
                client_id=client_config['web']['client_id'],
                client_secret=client_config['web']['client_secret'],
                refresh_token=decrypted_value)
        except InvalidGrantError:
            # The refresh token has expired or has been revoked
            abort(grpc.StatusCode.PERMISSION_DENIED,
                  self.AUTHZ_ERROR_MESSAGE.format(owner))

        return {
            'access_token': access_token['access_token'],
            'expires_at':
            self.calculate_expiry_time(access_token['expires_in'])
        }
Example #4
0
def renew_session_token(request, context):
    authenticated_user = authenticate_user(request, context)
    utils.validate_not_empty(request, 'session_token')

    # Retrieve the session details from the database
    try:
        session = get_session_from_token(request.session_token)
    except DatabaseObjectNotFound:
        abort(grpc.StatusCode.PERMISSION_DENIED,
              'Session token is invalid or has expired')

    # Verify that the caller is the authorized renewer for the token
    if session.renewer != authenticated_user:
        abort(grpc.StatusCode.PERMISSION_DENIED,
              f'Unauthorized renewer: {authenticated_user}')

    # Extends session's lifetime
    session.extend_lifetime()
    session.save()

    # Create and return RPC response
    response = broker_pb2.RenewSessionTokenResponse()
    response.expires_at = session.expires_at
    return response, {
        'owner': session.owner,
        'renewer': session.renewer,
        'session-id': session.id
    }
def read_session_token(token: str) -> (str, bytes):
    try:
        header, encrypted_password = token.encode('ascii').split(
            TOKEN_SEPARATOR)
    except (ValueError):
        abort(grpc.StatusCode.UNAUTHENTICATED, f'Invalid session token')
    header = base64.urlsafe_b64decode(header).decode('ascii')
    session_id = json.loads(header)['session_id']
    encrypted_password = base64.urlsafe_b64decode(encrypted_password)
    return session_id, encrypted_password
 def _get_spnego_token(self, context):
     # Extract the client-supplied SPNEGO token from the "Authorization" header
     # provided in the gRPC request metadata
     metadata = context.invocation_metadata()
     header = dict(metadata).get("authorization")
     if header is None or not header.startswith('Negotiate '):
         abort(
             grpc.StatusCode.UNAUTHENTICATED,
             'Use "authorization: Negotiate <token>" metadata to authenticate'
         )
     spnego_token = header.split()[1]
     return spnego_token
def authenticate_session(context) -> None:
    metadata = context.invocation_metadata()
    header = dict(metadata).get("authorization")
    if header is None or not header.startswith('BrokerSession '):
        return None
    else:
        token = header.split()[1]
        session = get_session_from_token(token)
        if session.is_expired():
            abort(grpc.StatusCode.UNIMPLEMENTED,
                  f'Expired session ID: {session_id}')
        return session
 def authenticate(self, request, context):
     spnego_token = self._get_spnego_token(context)
     gcssapi_context = None
     try:
         # Initialize GSSAPI context
         result, gcssapi_context = kerberos.authGSSServerInit(
             f'{settings.BROKER_SERVICE_NAME}@{settings.BROKER_SERVICE_HOSTNAME}'
         )
         if result != kerberos.AUTH_GSS_COMPLETE:
             # The GSSAPI context initialization failed
             abort(grpc.StatusCode.PERMISSION_DENIED)
         # Process the client-supplied SPNEGO token
         result = kerberos.authGSSServerStep(gcssapi_context, spnego_token)
         if result == kerberos.AUTH_GSS_COMPLETE:
             # Authentication succeded. Return the authenticated principal's username.
             principal = kerberos.authGSSServerUserName(gcssapi_context)
             return principal
         else:
             # Authentication failed
             abort(grpc.StatusCode.PERMISSION_DENIED)
     except kerberos.GSSError:
         abort(grpc.StatusCode.PERMISSION_DENIED)
     finally:
         # Destroy the GSSAPI context
         if gcssapi_context:
             kerberos.authGSSServerClean(gcssapi_context)
Example #9
0
def validate_impersonator(impersonator, impersonated):
    whitelist = set(settings.PROXY_USER_WHITELIST.split(','))
    if impersonator != impersonated and impersonator not in whitelist:
        abort(grpc.StatusCode.PERMISSION_DENIED,
              f'`{impersonator}` is not a whitelisted impersonator')
Example #10
0
def validate_domain(user):
    try:
        common_utils.validate_domain(user)
    except ValueError:
        abort(grpc.StatusCode.PERMISSION_DENIED,
              f'Invalid domain for user: {user}')
Example #11
0
def validate_not_empty(request, param):
    if getattr(request, param) == '':
        abort(grpc.StatusCode.INVALID_ARGUMENT,
              f'Request must provide the `{param}` parameter')
Example #12
0
def get_access_token(request, context):
    session = authenticate_session(context)
    if session is not None:
        utils.validate_not_empty(request, 'owner')
        utils.validate_not_empty(request, 'scope')
        if request.target != session.target:
            abort(grpc.StatusCode.PERMISSION_DENIED, "Target mismatch")
        if request.owner not in [session.owner, session.owner.split('@')[0]]:
            abort(grpc.StatusCode.PERMISSION_DENIED, "Owner mismatch")
        if request.scope != session.scope:
            abort(grpc.StatusCode.PERMISSION_DENIED, "Scope mismatch")
    else:
        authenticated_user = authenticate_user(request, context)
        utils.validate_not_empty(request, 'owner')
        utils.validate_not_empty(request, 'scope')
        utils.validate_impersonator(authenticated_user, request.owner)

    utils.validate_scope(request.scope)

    # Create cache key to look up access token from cache
    cache_key = f'access-token-{request.owner}-{request.scope}'

    # First check in local cache
    local_cache = get_local_cache()
    access_token = local_cache.get(cache_key)
    if access_token is None:
        # Not found in local cache, so look in remote cache.
        cache = get_cache()
        encrypted_access_token = cache.get(cache_key)
        if encrypted_access_token is not None:
            # Cache hit... Let's load the value.
            access_token_json = encryption.decrypt(
                settings.ENCRYPTION_ACCESS_TOKEN_CACHE_CRYPTO_KEY,
                encrypted_access_token)
            access_token = json.loads(access_token_json)
        else:
            # Cache miss... Let's generate a new access token.
            # Start by acquiring a lock to avoid cache stampede
            lock = cache.acquire_lock(f'{cache_key}_lock')
            try:
                # Check again if there's still no value
                encrypted_access_token = cache.get(cache_key)
                if encrypted_access_token is not None:
                    # This time it's a cache hit. The token must have been generated
                    # by a competing thread. So we just load the value.
                    access_token_json = encryption.decrypt(
                        settings.ENCRYPTION_ACCESS_TOKEN_CACHE_CRYPTO_KEY,
                        encrypted_access_token)
                    access_token = json.loads(access_token_json)
                else:
                    # Again a cache miss, so we must really generate a new token now.
                    access_token = get_provider().get_access_token(
                        request.owner, request.scope)
                    # Cache token for possible future requests
                    access_token_json = json.dumps(access_token)
                    encrypted_value = encryption.encrypt(
                        settings.ENCRYPTION_ACCESS_TOKEN_CACHE_CRYPTO_KEY,
                        access_token_json)
                    cache.set(
                        cache_key, encrypted_value,
                        timedelta(
                            seconds=settings.ACCESS_TOKEN_REMOTE_CACHE_TIME))
            finally:
                # Release the lock
                cache.release_lock(lock)

        # Add unencrypted token to local cache
        local_cache.set(cache_key, access_token,
                        settings.ACCESS_TOKEN_LOCAL_CACHE_TIME)

    # Create and return RPC response
    response = broker_pb2.GetAccessTokenResponse()
    response.access_token = access_token['access_token']
    response.expires_at = access_token['expires_at']
    return response, {'owner': request.owner, 'scope': request.scope}
def get_session_from_token(token: str) -> Session:
    session_id, encrypted_password = read_session_token(token)
    session = Session.get(session_id)
    if not password_match(session, encrypted_password):
        abort(grpc.StatusCode.UNAUTHENTICATED, 'Invalid session token')
    return session