示例#1
0
async def authorize_card(request: Request,
                         card: CardModel = Body(...),
                         api_key_auth: str = Security(api_key)):
    """Authorize by card ID, requires configured Api Token."""
    if not config.oauth2.card_authentication_api_key:
        raise HTTPException(500, "Not configured")
    if api_key_auth != config.oauth2.card_authentication_api_key:
        raise HTTPException(403)

    # Find user by card id and authorize by that.
    user_data = await async_user_collection.find_one({'card_id': card.card_id})
    if user_data is None:
        raise HTTPException(404, "Unknown card")
    user = User.validate(user_data)
    if not user.active:
        raise HTTPException(400, "User not active")

    user_with_groups = await UserWithRoles.async_load_groups(
        user, config.oauth2.card_authentication_client_id)
    request = await oauth2_request(request)
    request.data['client_id'] = config.oauth2.card_authentication_client_id
    resp = await run_in_threadpool(
        authorization.create_authorization_response,
        request=request,
        grant_user=user_with_groups,
    )
    if isinstance(resp, ErrorJSONResponse):
        return resp
    if isinstance(resp, RedirectResponse):
        return resp.to_json_response()
    assert not isinstance(resp, JSONResponse)
示例#2
0
    async def async_load_all(client_id: str) -> AsyncIterator[User]:
        client = await async_client_collection.find_one({'_id': client_id}, {
            '_id': 0,
            'access_groups': 1
        })
        if client is None:
            raise HTTPException(400, "Invalid client")

        client_groups = [
            access_group for access_group in client['access_groups']
        ]
        client_user_groups = [
            access_group['group'] for access_group in client_groups
        ]

        all_client_group_maps = await _async_resolve_groups(client_user_groups)
        all_client_groups = set(group
                                for groups in all_client_group_maps.values()
                                for group in groups)

        async for user_data in async_user_collection.find(
            {'groups': {
                '$in': all_client_groups
            }}):
            yield User.validate(user_data)
示例#3
0
 def load(user_id: str, client_id: str) -> Optional['UserWithRoles']:
     user_data = user_collection.find_one({'_id': user_id})
     if user_data is None:
         return None
     user = User.validate(user_data)
     if not user.active:
         raise HTTPException(401, "User inactive")
     return UserWithRoles.load_groups(user, client_id)
示例#4
0
async def upload_picture(
        user_id: str,
        file: UploadFile = File(..., media_type='application/octet-stream'),
        user: UserInfo = Depends(Authentication(auto_error=False)),
        registration_token: Optional[str] = Header(None, alias="x-token"),
):
    """Uploads a new picture for the passed user."""
    if user is not None:
        is_admin = 'admin' in user['roles']
        is_self = user.sub == user_id
        if not is_self or not is_admin:
            raise HTTPException(401)
        user_data = await async_user_collection.find_one({'_id': user_id})
    elif registration_token is not None:
        check_token(registration_token)
        user_data = await async_user_collection.find_one(
            {'registration_token': registration_token})
    else:
        raise HTTPException(401)
    if user_data is None:
        raise HTTPException(404)
    user = User.validate(user_data)
    if user.picture is None:
        user.picture = generate_token(48)
        updated_at = int(time.time())
        await async_user_collection.update_one(
            {'_id': user_id},
            {'$set': {
                'picture': user.picture,
                'updated_at': updated_at
            }})
        await async_client_user_cache_collection.update_many(
            {'user_id': user_data['_id']},
            {'$set': {
                'last_modified': updated_at
            }},
        )
    else:
        try:
            await async_user_picture_bucket.delete(user.picture)
        except gridfs.errors.NoFile:
            pass
    hash_ = hashlib.sha512()
    while True:
        chunk = await file.read(4 * 1024)
        if not chunk:
            break
        hash_.update(chunk)
    await file.seek(0)
    file.file.seek(0)
    await async_user_picture_bucket.upload_from_stream_with_id(
        user.picture,
        user.id,
        file.file,
        metadata={
            'content_type': file.content_type,
            'hash': hash_.digest()
        })
示例#5
0
 def authenticate_user(self, username: str, password: str):
     user_data = user_collection.find_one({
         'email': username,
         'access_tokens.token': password,
         'active': True
     })
     if user_data is None:
         return None
     return UserWithRoles.load_groups(User.validate(user_data),
                                      self.client.id)
示例#6
0
            ],
        ).dict(exclude_none=True, by_alias=True))
    mongo.user_group_collection.insert_one(
        UserGroup(
            id='users',
            visible=False,
            group_name="All Users",
            members=[admin_id],
        ).dict(exclude_none=True, by_alias=True))
    mongo.user_group_collection.insert_one(
        UserGroup(
            id='admin',
            visible=False,
            group_name="Admins",
            members=[admin_id],
        ).dict(exclude_none=True, by_alias=True))
    registration_token = create_token(
        admin_id, now + config.manager.token_valid.registration)
    mongo.user_collection.insert_one(
        User(
            id=admin_id,
            email="admin@localhost",
            active=False,
            email_verified=True,
            groups=['users', 'admin'],
            updated_at=now,
            registration_token=registration_token,
        ).dict(exclude_none=True, by_alias=True))
    print("Use the following link to register the administrator:")
    print(f"{config.manager.frontend_base_url}/register/{registration_token}")
async def update_user(
    user_data: DotDict,
    update_data: Dict[str, Any],
    is_new: bool = False,
    is_registering: bool = False,
    is_admin: bool = False,
    is_self: bool = False,
    no_registration: bool = False,
):
    if 'sub' in update_data or '_id' in update_data or 'picture' in update_data:
        raise HTTPException(400, f"Cannot modify 'sub', '_id' or 'picture'")
    was_active = user_data.get('active', False)
    reset_user_cache = False

    if is_new:
        assert '_id' not in user_data
        user_data['_id'] = generate_token(48)

    if 'password' in update_data:
        if not isinstance(update_data['password'], str):
            raise HTTPException(400, "'password' must be a string")
        _validate_property_write('password', is_self, is_admin)
        if is_self and not is_registering and user_data.get(
                'password') is not None:
            if 'old_password' not in update_data:
                raise HTTPException(
                    400, f"Need {repr('old_password')} for setting password")
            if not isinstance(update_data['old_password'], str):
                raise HTTPException(400,
                                    f"{repr('old_password')} is not a string")
            is_valid, _ = verify_and_update(update_data['old_password'],
                                            user_data['password'])
            if not is_valid:
                raise HTTPException(401, "Old password does not match")
        try:
            user_data['password'] = create_password(update_data['password'])
            del update_data['password']
        except PasswordLeakedException:
            raise HTTPException(
                400,
                "Password is leaked and cannot be used. See https://haveibeenpwned.com/"
            )

    async def send_mail():
        pass

    if is_registering and update_data.get(
            'email', user_data['email']) == user_data['email']:
        user_data['email_verified'] = True
    elif 'email' in update_data:
        if not isinstance(update_data['email'], str):
            raise HTTPException(400, "'email' must be a string")
        _validate_property_write('email', is_self, is_admin)
        if not is_email(update_data['email'], check_dns=True):
            raise HTTPException(400, "E-Mail address not accepted")
        if await async_user_collection.count_documents(
            {'email': update_data['email']}, limit=1) != 0:
            raise HTTPException(
                400,
                "E-Mail address already in use, please use existing account")
        new_mail = update_data['email']
        locale = update_data.get(
            'locale',
            user_data.get('locale',
                          config.oauth2.user.properties['locale'].default))
        if locale is None:
            locale = 'en_us'
        tz = _get_tz(update_data.get('zoneinfo', user_data.get('zoneinfo')))
        del update_data['email']
        if is_new and not no_registration:
            user_data['email'] = new_mail
            user_data['email_verified'] = False
            token_valid_until = int(time.time() +
                                    config.manager.token_valid.registration)
            user_data['registration_token'] = create_token(
                user_data['_id'], token_valid_until)

            async def send_mail():
                await async_send_mail_register(user_data, token_valid_until,
                                               locale, tz)
        elif not is_admin:
            token_valid_until = int(time.time() +
                                    config.manager.token_valid.email_set)
            user_data['email_verification_token'] = create_token(
                new_mail, token_valid_until)
            if is_registering:
                user_data['email'] = new_mail
                user_data['email_verified'] = False

            async def send_mail():
                await async_send_mail_verify(locale, new_mail, user_data,
                                             token_valid_until, tz)
        else:
            user_data['email'] = new_mail
            user_data['email_verified'] = False

    if 'access_tokens' in update_data:
        if not isinstance(update_data['access_tokens'], list):
            raise HTTPException(400, "'access_tokens' must be a list")
        try:
            access_tokens = [
                ValidateAccessToken.validate(val)
                for val in update_data['access_tokens']
            ]
        except ValueError as err:
            raise HTTPException(400, str(err))
        _validate_property_write('access_tokens', is_self, is_admin)
        existing_access_tokens = [
            UserPasswordAccessToken.validate(access_token)
            for access_token in user_data.get('access_tokens', [])
        ]
        existing_access_tokens_by_id = {
            existing_access_token.id: existing_access_token
            for existing_access_token in existing_access_tokens
        }
        new_access_tokens = []
        for access_token in access_tokens:
            if access_token.id is not None:
                store_token = existing_access_tokens_by_id.get(access_token.id)
                if store_token is None:
                    raise HTTPException(400,
                                        f"Invalid token ID {access_token.id}")
                store_token.description = access_token.description
                if access_token.token is not None:
                    store_token.token = access_token.token
            else:
                store_token = UserPasswordAccessToken(
                    id=generate_token(24),
                    description=access_token.description,
                    token=access_token.token,
                )
            new_access_tokens.append(store_token)
        del update_data['access_tokens']
        user_data['access_tokens'] = [
            access_token.dict() for access_token in new_access_tokens
        ]

    if 'groups' in update_data:
        if await _update_groups(
                user_data,
                update_data,
                property='groups',
                is_self=is_self,
                is_admin=is_admin,
                existence_check_property=None,
                groups_add_property='members',
                groups_pull_properties=(
                    'members',
                    'email_allowed_forward_members',
                    'email_forward_members',
                    'email_postbox_access_members',
                ),
                members_pull_properties=(
                    'email_allowed_forward_members',
                    'email_forward_members',
                    'email_postbox_access_members',
                ),
        ):
            reset_user_cache = True

    if 'email_allowed_forward_groups' in update_data:
        await _update_groups(
            user_data,
            update_data,
            property='email_allowed_forward_groups',
            is_self=is_self,
            is_admin=is_admin,
            existence_check_property='groups',
            groups_add_property='email_allowed_forward_members',
            groups_pull_properties=('email_allowed_forward_members',
                                    'email_forward_members'),
            members_pull_properties=('email_forward_members', ))

    if 'email_forward_groups' in update_data:
        await _update_groups(
            user_data,
            update_data,
            property='email_forward_groups',
            is_self=is_self,
            is_admin=is_admin,
            existence_check_property='email_allowed_forward_groups',
            groups_add_property='email_forward_members',
            groups_pull_properties=('email_forward_members', ),
        )

    if 'email_postbox_access_groups' in update_data:
        await _update_groups(
            user_data,
            update_data,
            property='email_postbox_access_groups',
            is_self=is_self,
            is_admin=is_admin,
            existence_check_property='groups',
            groups_add_property='email_postbox_access_members',
            groups_pull_properties=('email_postbox_access_members', ),
        )

    for key, value in update_data.items():
        _validate_property_write(key, is_self, is_admin)
        prop = config.oauth2.user.properties[key]
        if prop.write_once and user_data.get(key) is not None:
            raise HTTPException(400, f"{repr(key)} can only be set once")
        if not value and not prop.required and key in user_data:
            del user_data[key]
        if prop.type in (UserPropertyType.str, UserPropertyType.multistr,
                         UserPropertyType.token):
            if not isinstance(value, str):
                raise HTTPException(
                    400, f"{repr(key)}={repr(value)} is not a string")
            if prop.template is not None:
                raise HTTPException(400,
                                    f"{repr(key)}={repr(value)} is generated")
            if prop.format is not None:
                regex = get_regex(prop.format)
                if not regex.fullmatch(value):
                    raise HTTPException(
                        400,
                        f"{repr(key)}={repr(value)} does not match pattern {repr(regex.pattern)}"
                    )
            user_data[key] = value
        elif prop.type == UserPropertyType.int:
            if isinstance(value, float):
                if not value.is_integer():
                    raise HTTPException(
                        400, f"{repr(key)}={repr(value)} is not an integer")
                value = int(value)
            if not isinstance(value, int):
                raise HTTPException(
                    400, f"{repr(key)}={repr(value)} is not an integer")
            user_data[key] = value
        elif prop.type == UserPropertyType.bool:
            if not isinstance(value, bool):
                raise HTTPException(
                    400, f"{repr(key)}={repr(value)} is not a boolean")
            user_data[key] = value
        elif prop.type == UserPropertyType.datetime:
            if not isinstance(value, str):
                raise HTTPException(
                    400, f"{repr(key)}={repr(value)} is not a datetime string")
            try:
                parse_datetime(value)
                user_data[key] = value
            except ValueError:
                raise HTTPException(
                    400, f"{repr(key)}={repr(value)} is not a datetime string")
        elif prop.type == UserPropertyType.date:
            if not isinstance(value, str):
                raise HTTPException(
                    400, f"{repr(key)}={repr(value)} is not a date string")
            try:
                parse_date(value)
                user_data[key] = value
            except ValueError:
                raise HTTPException(
                    400, f"{repr(key)}={repr(value)} is not a datetime string")
        elif prop.type == UserPropertyType.enum:
            if not isinstance(value, str):
                raise HTTPException(
                    400, f"{repr(key)}={repr(value)} is not a string")
            assert prop.values is not None
            values = [enum_value.value for enum_value in prop.values]
            if value not in values:
                raise HTTPException(
                    400,
                    f"{repr(key)}={repr(value)} is not a valid enum value")
            user_data[key] = value
        else:
            raise NotImplementedError(f"{repr(prop.type)}")

    # Set others to default
    if is_new or is_registering:
        for key, value in config.oauth2.user.properties.items():
            if value.default is not None and key not in user_data:
                user_data[key] = value.default

    # Activate the user after registration
    if is_registering:
        user_data['active'] = True

    # Apply all templates and validate required, when not active
    if user_data.get('active', False):
        # Validate that all required variables are present
        for key, value in config.oauth2.user.properties.items():
            if value.required and user_data.get(key) is None:
                raise HTTPException(400, f"Missing {repr(key)}")
        # Apply templates (they should not be required)
        for key, value in config.oauth2.user.properties.items():
            if (value.type == UserPropertyType.str
                    and value.template is not None
                    and (not value.write_once or not user_data.get(key))):
                assert "'''" not in value.template, f"Invalid ''' in template: {value.template}"
                user_data[key] = eval(
                    f"f'''{value.template}'''",
                    {
                        'make_username': make_username,
                        'config': config
                    },
                    user_data,
                )

    user_data['updated_at'] = int(time.time())

    User.validate(user_data)

    if is_new:
        await async_user_collection.insert_one(user_data)
    else:
        await async_user_collection.replace_one({'_id': user_data['_id']},
                                                user_data)
    if user_data.get('active', False):
        if reset_user_cache:
            await async_client_user_cache_collection.delete_many(
                {'user_id': user_data['_id']})
        else:
            await async_client_user_cache_collection.update_many(
                {'user_id': user_data['_id']},
                {'$set': {
                    'last_modified': user_data['updated_at']
                }},
            )
    elif was_active:
        await async_client_user_cache_collection.delete_many(
            {'user_id': user_data['_id']})
        await async_token_collection.delete_many({'user_id': user_data['_id']})
        await async_session_collection.delete_many(
            {'user_id': user_data['_id']})
        await async_authorization_code_collection.delete_many(
            {'user_id': user_data['_id']})
    elif reset_user_cache:
        await async_client_user_cache_collection.delete_many(
            {'user_id': user_data['_id']})
    # Last: Send the email if there is one
    await send_mail()
示例#8
0
            new_id = user.uid
        else:
            new_id = generate_token(48)
        user_mapping[user.uid] = new_id

        users.append(
            User(
                id=new_id,
                email=user.email,
                active=True,
                email_verified=user.email_verified,
                notes=f"Imported '{user.uid}' from OC",
                groups=user.groups + ['users'],
                locale=user.language,
                zoneinfo=user.timezone,
                password=user.password,
                phone_number=user.phone,
                phone_number_verified=False,
                updated_at=int(time.time()),
                preferred_username=preferred_username,
                given_name=given_name,
                family_name=family_name,
                # This token is not usable, but it enforces that the user reviews the user credentials before logging in
                registration_token='imported',
            ).dict(exclude_none=True, by_alias=True))
        print("Create", users[-1])

    groups = []
    for group in reader.read_groups():
        members = [user_mapping[member] for member in group.members]
        gid = group.gid.lower()
async def authorize(
        request: Request,
        _query_params: OAuthAuthorizeRequestQueryParams = Depends(
            OAuthAuthorizeRequestQueryParams),
        auth_credentials: AuthCredentials = Body(...),
):
    """Perform authorization from given credentials and returns the result."""
    retry_delay, retry_after = await async_throttle(request)
    if retry_delay is not None and retry_after is not None:
        raise HTTPException(403,
                            "Wait",
                            headers={
                                'X-Retry-After': retry_after,
                                'X-Retry-Wait': retry_delay
                            })
    potential_users = async_user_collection.find(
        {'email': auth_credentials.email})
    async for potential_user in potential_users:
        if potential_user.get('password') is not None:
            password_valid, new_hash = verify_and_update(
                auth_credentials.password, potential_user['password'])
            if password_valid:
                user_data = potential_user
                break
    else:
        retry_after, retry_delay = await async_throttle_failure_request(request
                                                                        )
        raise HTTPException(403,
                            "Invalid E-Mail or Password",
                            headers={
                                'X-Retry-After': retry_after,
                                'X-Retry-Wait': retry_delay
                            })

    user = User.validate(user_data)
    if new_hash is not None:
        await async_user_collection.update_one(
            {'_id': user.id}, {'$set': {
                'password': new_hash
            }})
        user.password = new_hash
    if user.registration_token:
        # If the user password is correct, but a registration token is pending, redirect to registration page
        from ...manager.api.user_helpers import check_token, create_token
        import urllib.parse
        registration_token = user.registration_token
        try:
            check_token(user.registration_token)
        except HTTPException:
            token_valid_until = int(time.time() +
                                    config.manager.token_valid.registration)
            registration_token = create_token(user_data['_id'],
                                              token_valid_until)
            await async_user_collection.update_one(
                {'_id': user.id},
                {'$set': {
                    'registration_token': registration_token
                }},
            )
        args = dict(url_decode(request.url.query))
        args.update((await request.form()) or {})

        #: dict of query and body params
        return_url = urllib.parse.quote_plus(config.oauth2.base_url +
                                             '/authorize?' +
                                             url_encode(args.items()))
        return JSONResponse(
            content={
                'redirect_uri':
                f"{config.manager.frontend_base_url}/register/{registration_token}"
                f"?return_url={return_url}"
            },
            status_code=200,
            headers=dict(default_json_headers),
        )
    user_group_data = await UserWithRoles.async_load_groups(
        user, _query_params.client_id)
    oauth_request = await oauth2_request(request)
    resp = await run_in_threadpool(
        authorization.create_authorization_response,
        request=oauth_request,
        grant_user=user_group_data,
    )
    if isinstance(resp, ErrorJSONResponse):
        return resp
    elif isinstance(resp, ErrorRedirectResponse):
        return resp.to_json_response()
    elif isinstance(resp, RedirectResponse):
        expires_in = config.oauth2.token_expiration.session
        add_session_state(resp, oauth_request.user)
        resp = resp.to_json_response()
        if auth_credentials.remember:
            now = int(time.time())
            sess = Session(
                id=generate_token(config.oauth2.token_length),
                user_id=user.id,
                issued_at=now,
                expires_in=expires_in,
                expiration_time=datetime.utcnow() +
                timedelta(seconds=expires_in),
            )
            await async_session_collection.insert_one(
                sess.dict(exclude_none=True, by_alias=True))
            update_session_sid(resp, sess.id)
        update_session_state(resp, oauth_request.user)
        return resp
    assert False