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 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: 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, last_used=c.last_used) for c in user.credentials ])
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 update(call: APICall, company_id: str, _): 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 revoke_credentials(call: APICall, _, __): identity = call.identity access_key = call.data_model.access_key if _is_protected_user(call.identity.user): raise errors.bad_request.InvalidUserId("protected identity") 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 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, api_version=str(ServiceRepo.max_endpoint_version()), server_version=str(get_version()), server_build=str(get_build_number()), feature_set="basic", ) return GetTokenResponse(token=token.decode("ascii"))
def create_user(request: CreateUserRequest, call: APICall = None) -> str: """ Create a new user in both the auth database and the backend database :param request: New user details :param call: API call that triggered this call. If not None, new backend user creation will be performed using a new call in the same transaction. :return: The new user's ID """ with translate_errors_context(): if not Company.objects(id=request.company).only("id"): raise errors.bad_request.InvalidId(company=request.company) user = User( id=database.utils.id(), name=request.name, company=request.company, role=request.role or Role.user, email=request.email, created=datetime.utcnow(), ) user.save() users_create_request = Users_CreateRequest( id=user.id, name=request.name, company=request.company, family_name=request.family_name, given_name=request.given_name, avatar=request.avatar, ) try: UserBLL.create(users_create_request) except Exception as ex: user.delete() raise errors.server_error.GeneralError( "failed adding new user", ex=str(ex)) return user.id
def validate_impersonation(endpoint, call): """ Validate impersonation headers and set impersonated identity and authorization data accordingly. :returns True if 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 as ex: log.exception(f"Validating impersonation: {str(ex)}") raise errors.server_error.InternalError("validating impersonation")
def authorize_credentials(auth_data, service, action, call): """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 FixedUser.pass_hashed(): if not compare_secret_key_hash(secret_key, fixed_user.password): raise errors.unauthorized.InvalidCredentials( "bad username or password") else: 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(), "set__credentials__$__last_used_from": call.get_worker(default=call.real_ip), }) 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