def authorize_credentials(auth_data, service, action, call_data_items): """ Validate credentials against service/action and request data (dicts). Returns a new basic object (auth payload) """ try: access_key, _, secret_key = base64.b64decode( auth_data.encode()).decode('latin-1').partition(':') except Exception as e: log.exception('malformed credentials') raise errors.unauthorized.BadCredentials(str(e)) query = Q( credentials__match=Credentials(key=access_key, secret=secret_key)) fixed_user = None if FixedUser.enabled(): fixed_user = FixedUser.get_by_username(access_key) if fixed_user: if secret_key != fixed_user.password: raise errors.unauthorized.InvalidCredentials( 'bad username or password') if fixed_user.is_guest and not FixedUser.is_guest_endpoint( service, action): raise errors.unauthorized.InvalidCredentials( 'endpoint not allowed for guest') query = Q(id=fixed_user.user_id) with TimingContext( "mongo", "user_by_cred"), translate_errors_context('authorizing request'): user = User.objects(query).first() if not user: raise errors.unauthorized.InvalidCredentials( 'failed to locate provided credentials') if not fixed_user: # In case these are proper credentials, update last used time User.objects(id=user.id, credentials__key=access_key).update( **{"set__credentials__$__last_used": datetime.utcnow()}) with TimingContext("mongo", "company_by_id"): company = Company.objects(id=user.company).only('id', 'name').first() if not company: raise errors.unauthorized.InvalidCredentials('invalid user company') identity = Identity(user=user.id, company=user.company, role=user.role, user_name=user.name, company_name=company.name) basic = Basic(user_key=access_key, identity=identity) return basic
def _ensure_auth_user(user_data: dict, company_id: str, log: Logger, revoke: bool = False): key, secret = user_data.get("key"), user_data.get("secret") if not (key and secret): credentials = None else: creds = Credentials(key=key, secret=secret) user = AuthUser.objects(credentials__match=creds).first() if user: if revoke: user.credentials = [] user.save() return user.id credentials = [] if revoke else [creds] user_id = user_data.get("id", f"__{user_data['name']}__") log.info(f"Creating user: {user_data['name']}") user = AuthUser( id=user_id, name=user_data["name"], company=company_id, role=user_data["role"], email=user_data["email"], created=datetime.utcnow(), credentials=credentials, ) user.save() return user.id
def delete_user(identity: Identity, user_id: str, company_id: str = None, call: APICall = None): """ Delete an existing user from both the auth database and the backend database :param identity: Calling user identity :param user_id: ID of user to delete :param company_id: Company of user to delete :param call: API call that triggered this call. If not None, backend user deletion will be performed using a new call in the same transaction. """ if user_id == identity.user: raise errors.bad_request.FieldsValueError("cannot delete yourself", user=user_id) if not company_id: company_id = identity.company if (identity.role not in Role.get_system_roles() and company_id != identity.company): raise errors.bad_request.FieldsNotAllowedForRole( "must be empty or your own company", role=identity.role, field="company") with translate_errors_context(): query = dict(id=user_id, company=company_id) res = User.objects(**query).delete() if not res: raise errors.bad_request.InvalidUserId(**query) try: UserBLL.delete(user_id) except Exception as ex: log.error(f"Exception calling users.delete: {str(ex)}")
def _ensure_auth_user(user_data: dict, company_id: str, log: Logger, revoke: bool = False): ensure_credentials = {"key", "secret"}.issubset(user_data) if ensure_credentials: user = AuthUser.objects(credentials__match=Credentials( key=user_data["key"], secret=user_data["secret"])).first() if user: if revoke: user.credentials = [] user.save() return user.id user_id = user_data.get("id", f"__{user_data['name']}__") log.info(f"Creating user: {user_data['name']}") user = AuthUser( id=user_id, name=user_data["name"], company=company_id, role=user_data["role"], email=user_data["email"], created=datetime.utcnow(), credentials=[ Credentials(key=user_data["key"], secret=user_data["secret"]) ] if not revoke else [] if ensure_credentials else None, ) user.save() return user.id
def register_worker( self, company_id: str, user_id: str, worker: str, ip: str = "", queues: Sequence[str] = None, timeout: int = 0, tags: Sequence[str] = None, ) -> WorkerEntry: """ Register a worker :param company_id: worker's company ID :param user_id: user ID under which this worker is running :param worker: worker ID :param ip: the real ip of the worker :param queues: queues reported as being monitored by the worker :param timeout: registration expiration timeout in seconds :param tags: a list of tags for this worker :raise bad_request.InvalidUserId: in case the calling user or company does not exist :return: worker entry instance """ key = WorkerBLL._get_worker_key(company_id, user_id, worker) timeout = timeout or DEFAULT_TIMEOUT queues = queues or [] with translate_errors_context(): query = dict(id=user_id, company=company_id) user = User.objects(**query).only("id", "name").first() if not user: raise bad_request.InvalidUserId(**query) company = Company.objects(id=company_id).only("id", "name").first() if not company: raise server_error.InternalError("invalid company", company=company_id) queue_objs = Queue.objects(company=company_id, id__in=queues).only("id") if len(queue_objs) < len(queues): invalid = set(queues).difference(q.id for q in queue_objs) raise bad_request.InvalidQueueId(ids=invalid) now = datetime.utcnow() entry = WorkerEntry( key=key, id=worker, user=user.to_proper_dict(), company=company.to_proper_dict(), ip=ip, queues=queues, register_time=now, register_timeout=timeout, last_activity_time=now, tags=tags, ) self.redis.setex(key, timedelta(seconds=timeout), entry.to_json()) return entry
def get_credentials(call): """ Validate a user by his email. INTERNAL. """ assert isinstance(call, APICall) identity = call.identity with translate_errors_context(): query = dict(id=identity.user, company=identity.company) user = User.objects(**query).first() if not user: raise errors.bad_request.InvalidUserId(**query) # we return ONLY the key IDs, never the secrets (want a secret? create new credentials) call.result.data_model = GetCredentialsResponse(credentials=[ CredentialsResponse(access_key=c.key) for c in user.credentials ])
def revoke_credentials(call): assert isinstance(call, APICall) identity = call.identity access_key = call.data_model.access_key with translate_errors_context(): query = dict( id=identity.user, company=identity.company, credentials__key=access_key ) updated = User.objects(**query).update_one(pull__credentials__key=access_key) if not updated: raise errors.bad_request.InvalidUser( "invalid user or invalid access key", **query ) call.result.data_model = RevokeCredentialsResponse(revoked=updated)
def create_credentials(cls, user_id: str, company_id: str, role: str = None) -> CredModel: with translate_errors_context(): query = dict(id=user_id, company=company_id) user = User.objects(**query).first() if not user: raise errors.bad_request.InvalidUserId(**query) cred = CredModel(access_key=get_client_id(), secret_key=get_secret_key()) user.credentials.append( Credentials(key=cred.access_key, secret=cred.secret_key)) user.save() return cred
def update(call, company_id, _): assert isinstance(call, APICall) fields = { k: v for k, v in call.data_model.to_struct().items() if k != "user" and v is not None } with translate_errors_context(): result = User.objects(company=company_id, id=call.data_model.user).update( **fields, full_result=True, upsert=False ) if not result.matched_count: raise errors.bad_request.InvalidUserId() call.result.data_model = UpdateResponse( updated=result.modified_count, fields=fields )
def _ensure_user(user_data, company_id): user = User.objects(credentials__match=Credentials( key=user_data["key"], secret=user_data["secret"])).first() if user: return user.id log.info(f"Creating user: {user_data['name']}") user = User( id=f"__{user_data['name']}__", name=user_data["name"], company=company_id, role=user_data["role"], email=user_data["email"], created=datetime.utcnow(), credentials=[ Credentials(key=user_data["key"], secret=user_data["secret"]) ], ) user.save() return user.id
def get_token_for_user( user_id: str, company_id: str = None, expiration_sec: int = None, entities: dict = None, ) -> GetTokenResponse: with translate_errors_context(): query = dict(id=user_id) if company_id: query.update(company=company_id) user = User.objects(**query).first() if not user: raise errors.bad_request.InvalidUserId(**query) company_id = company_id or user.company company = Company.objects(id=company_id).only("id", "name").first() if not company: raise errors.bad_request.InvalidId( "invalid company associated with user", company=company) identity = Identity( user=user_id, company=company_id, role=user.role, user_name=user.name, company_name=company.name, ) token = Token.create_encoded_token( identity=identity, entities=entities, expiration_sec=expiration_sec, ) return GetTokenResponse(token=token.decode("ascii"))
def _ensure_auth_user(user_data, company_id): ensure_credentials = {"key", "secret"}.issubset(user_data.keys()) if ensure_credentials: user = AuthUser.objects(credentials__match=Credentials( key=user_data["key"], secret=user_data["secret"])).first() if user: return user.id log.info(f"Creating user: {user_data['name']}") user = AuthUser( id=user_data.get("id", f"__{user_data['name']}__"), name=user_data["name"], company=company_id, role=user_data["role"], email=user_data["email"], created=datetime.utcnow(), credentials=[ Credentials(key=user_data["key"], secret=user_data["secret"]) ] if ensure_credentials else None, ) user.save() return user.id
def authorize_credentials(auth_data, service, action, call_data_items): """ Validate credentials against service/action and request data (dicts). Returns a new basic object (auth payload) """ try: access_key, _, secret_key = base64.b64decode( auth_data.encode()).decode('latin-1').partition(':') except Exception as e: log.exception('malformed credentials') raise errors.unauthorized.BadCredentials(str(e)) with TimingContext( "mongo", "user_by_cred"), translate_errors_context('authorizing request'): user = User.objects(credentials__match=Credentials( key=access_key, secret=secret_key)).first() if not user: raise errors.unauthorized.InvalidCredentials( 'failed to locate provided credentials') with TimingContext("mongo", "company_by_id"): company = Company.objects(id=user.company).only('id', 'name').first() if not company: raise errors.unauthorized.InvalidCredentials('invalid user company') identity = Identity(user=user.id, company=user.company, role=user.role, user_name=user.name, company_name=company.name) basic = Basic(user_key=access_key, identity=identity) return basic
def validate_impersonation(endpoint, call): """ Validate impersonation headers and set impersonated identity and authorization data accordingly. :returns True is impersonating, False otherwise """ try: act_as = call.act_as impersonate_as = call.impersonate_as if not impersonate_as and not act_as: return elif impersonate_as and act_as: raise errors.bad_request.InvalidHeaders( "only one allowed", headers=tuple(call.impersonation_headers.keys())) identity = call.auth.identity # verify this user is allowed to impersonate at all if identity.role not in Role.get_system_roles() | {Role.admin}: raise errors.bad_request.ImpersonationError( "impersonation not allowed", role=identity.role) # get the impersonated user's info user_id = act_as or impersonate_as if identity.role in [Role.root]: # only root is allowed to impersonate users in other companies query = dict(id=user_id) else: query = dict(id=user_id, company=identity.company) user = User.objects(**query).first() if not user: raise errors.bad_request.ImpersonationError( "unknown user", **query) company = Company.objects(id=user.company).only("name").first() if not company: query.update(company=user.company) raise errors.bad_request.ImpersonationError( "unknown company for user", **query) # create impersonation payload if act_as: # act as a user, using your own role and permissions call.impersonation = Payload( auth_type=None, identity=Identity( user=user.id, company=user.company, role=identity.role, user_name=f"{identity.user_name} (acting as {user.name})", company_name=company.name, ), ) elif impersonate_as: # impersonate as a user, using his own identity and permissions (required additional validation to verify # impersonated user is allowed to access the endpoint) service, _, action = endpoint.name.partition(".") call.impersonation = authorize_impersonation( user=user, identity=Identity( user=user.id, company=user.company, role=user.role, user_name= f"{user.name} (impersonated by {identity.user_name})", company_name=company.name, ), service=service, action=action, call=call, ) else: return False return True except APIError: raise except Exception: raise errors.server_error.InternalError("validating impersonation")