Example #1
0
    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)}")
Example #2
0
    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
Example #3
0
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
        ])
Example #4
0
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
Example #5
0
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
    )
Example #6
0
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)
Example #7
0
    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
Example #8
0
    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"))
Example #9
0
    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
Example #10
0
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")
Example #11
0
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