Esempio n. 1
0
    def get(self, args) -> Response:
        try:
            token = oauth.auth.authorize_access_token(**args)
            id_token = oauth.auth.parse_id_token(token)
            subject = id_token.get("sub")
            if not subject:
                raise OAuthError(description="No user details provided")
        except OAuthError as e:
            return make_api_response(400, e.description)

        user: User = User.get(id=subject)

        preferred_username = id_token.get("preferred_username")
        if not user and preferred_username:
            valid = get_long_validation(preferred_username)
            if not valid.valid:
                token_body = make_linking_token(subject, preferred_username,
                                                valid.reason)
                linking_token = create_access_token(token_body)
                return make_api_response(400,
                                         valid.reason,
                                         content={"token": linking_token})
            # make the user an admin if they are claimed to be an admin
            user = create_user(subject,
                               preferred_username,
                               admin=id_token.get("admin", False))

        if user:
            tokens = get_sign_in_body(user)
            return make_api_response(200, content=tokens)

        return make_api_response(400, "Unable to login using OpenID")
Esempio n. 2
0
 def delete(self, args: Dict[str, UUID]) -> Response:
     song = Song[args["id"]]
     if song in current_user.favourites:
         current_user.favourites.remove(song)
         return make_api_response(
             200, f'Removed "{song.title}" from your favourites')
     return make_api_response(400,
                              f'"{song.title}" is not in your favourites')
Esempio n. 3
0
 def get(self) -> Response:
     if current_user:
         content = {
             "username": current_user.username,
             "admin": current_user.admin,
         }
         return make_api_response(200, content=content)
     return make_api_response(500, "Failed to get user")
Esempio n. 4
0
 def put(self, args: Dict[str, UUID]) -> Response:
     song = Song[args["id"]]
     if song not in current_user.favourites:
         current_user.favourites.add(song)
         return make_api_response(
             200, f'Added "{song.title}" to your favourites')
     return make_api_response(
         400, f'"{song.title}" is already in your favourites')
Esempio n. 5
0
 def get(self) -> Response:
     try:
         redirect_uri = urljoin(request.url_root, "openid/callback")
         url = oauth.auth.create_authorization_url(redirect_uri)
         oauth.auth.save_authorize_data(request,
                                        redirect_uri=redirect_uri,
                                        **url)
         return make_api_response(200, content=url)
     except Exception as e:
         app.logger.exception("Unable to login using OpenID")
         return make_api_response(400, "Unable to login using OpenID")
Esempio n. 6
0
 def post(self, username: str, password: str) -> Response:
     validator = valid_username(username)
     if not validator.valid:
         return make_api_response(400, validator.reason)
     # if no users registered, make first one admin
     make_admin = User.select().count() == 0
     new_user = register(username, password, admin=make_admin)
     if new_user:
         return make_api_response(
             200, f'User "{username}" successfully registered')
     return make_api_response(500, "Failed to register user")
Esempio n. 7
0
    def post(self, username: str, token: str):
        decoded = decode_token(token)["identity"]

        valid = get_long_validation(username)
        if not valid.valid:
            decoded = make_linking_token(decoded["id"], username, valid.reason)
            return make_api_response(400, valid.reason, content=decoded)

        user = create_user(decoded["id"], username)
        tokens = get_sign_in_body(user)
        return make_api_response(200, content=tokens)
Esempio n. 8
0
 def post(self, args: Dict[str, UUID]) -> Response:
     if not app.config["PUBLIC_DOWNLOADS"]:
         if not current_user or not current_user.admin:
             return make_api_response(403, "Downloading is not enabled")
     song_id = args["id"]
     new_token = create_access_token({"id": str(song_id)},
                                     expires_delta=timedelta(seconds=10))
     return make_api_response(200,
                              content={
                                  "download_token": new_token,
                                  "id": song_id
                              })
Esempio n. 9
0
    def post(self) -> Response:
        if not app.config["PUBLIC_UPLOADS"]:
            if not user_is_admin():
                return make_api_response(403, "Uploading is not enabled")

        if "song" not in request.files:
            app.logger.warning("No file part")
            return make_api_response(400, "No `song` file field in request")

        song = request.files["song"]
        if song.filename == "":
            app.logger.warning("No selected file")
            return make_api_response(400, "No file selected")

        if allowed_file_extension(Path(song.filename)):
            filename = secure_filename(song.filename)
            if not filename:
                return make_api_response(400, "Filename not valid")

            filepath = Path(app.config["PATH_ENCODE"], filename)
            song.save(str(filepath))

            kind = filetype.guess(str(filepath))
            if not kind or kind.mime.split("/")[0] != "audio":
                filepath.unlink(missing_ok=True)
                return make_api_response(400, "File is not audio")

            meta = get_metadata(filepath)
            if not meta:
                filepath.unlink(missing_ok=True)
                return make_api_response(400, "File missing metadata")

            with concurrent.futures.ThreadPoolExecutor() as executor:
                try:
                    future = executor.submit(encode_file,
                                             filepath,
                                             remove_original=True)
                    final_path = future.result()
                    song = insert_song(final_path)
                    if song:
                        app.logger.info(f'File "{filename}" uploaded')
                        return make_api_response(200,
                                                 f'File "{filename}" uploaded',
                                                 content={"id": song.id})
                except EncodeError:
                    # delete the original
                    app.logger.exception(f"Encode error for {filepath}")
                    filepath.unlink(missing_ok=True)
                    return make_api_response(400, "File could not be encoded")
        return make_api_response(400, "File could not be processed")
Esempio n. 10
0
 def delete(self, args: Dict[str, UUID], **kwargs) -> Response:
     song = get_song_or_abort(args["id"])
     filepath = os.path.join(app.config["PATH_MUSIC"], song.filename)
     if os.path.isfile(filepath):
         os.remove(filepath)
     song.delete()
     app.logger.info(f'Deleted song "{song.filename}"')
     return make_api_response(
         200, f'Successfully deleted song "{song.filename}"')
Esempio n. 11
0
 def put(self, args: Dict[str, UUID], **kwargs) -> Response:
     if not request.json:
         return make_api_response(400, "No data provided")
     accepted_fields = ["artist", "title"]
     values = {
         field: val
         for field, val in request.json.items() if field in accepted_fields
     }
     song = get_song_or_abort(args["id"])
     song.set(**values)
     commit()
     # update file metadata as well
     metadata = mutagen.File(Path(app.config["PATH_MUSIC"], song.filename),
                             easy=True)
     metadata.update(values)
     metadata.save()
     return make_api_response(
         200,
         "Successfully updated song metadata",
         content=dataclasses.asdict(get_song_detailed(song)),
     )
Esempio n. 12
0
 def validate_response(status_code, error=None, body=None):
     resp = utils.make_api_response(
         status_code,
         description=body.get("description", None) if body else None,
         content=body,
     )
     if not body:
         body = {}
     json = {"status_code": status_code, "error": error, **body}
     assert resp.get_json() == json
     assert resp.status_code == status_code
     return True
Esempio n. 13
0
    def put(self, args: Dict[str, UUID]) -> Response:
        def get_meta():
            return SongMeta(
                **dataclasses.asdict(request_status(song)),
                favourited=False
                if not current_user else song in current_user.favourites,
            )

        song = Song[args.get("id")]
        status = request_status(song)
        if status.requestable:
            Queue(song=song, requested=True)
            return make_api_response(
                200,
                f'Requested "{song.title}" successfully',
                content={"meta": get_meta()},
            )
        return make_api_response(
            400,
            f'"{song.title}" is not requestable at this moment. {status.reason}',
            content={"meta": get_meta()},
        )
Esempio n. 14
0
def get_songs_response(
    context: rest.Resource,
    page: int,
    query: Optional[str],
    limit: int,
    favourites: Optional[User] = None,
) -> Response:
    results = query_songs(query, favourites)
    pagination = Pagination(page=page,
                            per_page=limit,
                            total_count=results.count())
    # report error if page does not exist
    if page <= 0 or page > pagination.pages:
        return make_api_response(404, "Page does not exist")
    processed_songs = list(map(get_song_detailed, results.page(page, limit)))
    args = partial(filter_default_webargs,
                   args=SongQuerySchema(),
                   query=query,
                   limit=limit)
    return make_api_response(
        200,
        content={
            "_links": {
                "_self":
                api.url_for(context, **args(page=page), _external=True),
                "_next":
                api.url_for(context, **args(
                    page=page +
                    1), _external=True) if pagination.has_next else None,
                "_prev":
                api.url_for(context, **args(
                    page=page -
                    1), _external=True) if pagination.has_prev else None,
            },
            "query": query,
            "pagination": pagination.to_json(),
            "songs": processed_songs,
        },
    )
Esempio n. 15
0
 def get(self, query: str) -> Response:
     data = []
     artists = select(s.artist for s in Song
                      if query.lower() in s.artist.lower())
     titles = select(s.title for s in Song
                     if query.lower() in s.title.lower())
     for entry in artists.limit(5):
         data.append({"result": entry, "type": "Artist"})
     for entry in titles.limit(5):
         data.append({"result": entry, "type": "Title"})
     return make_api_response(200,
                              content={
                                  "query": query,
                                  "suggestions": data
                              })
Esempio n. 16
0
def invalid_token_loader(error: str) -> Response:
    return make_api_response(400, error)
Esempio n. 17
0
 def get(self, args: Dict[str, UUID], **kwargs) -> Response:
     song = get_song_or_abort(args["id"])
     return make_api_response(200,
                              content=dataclasses.asdict(
                                  get_song_detailed(song)))
Esempio n. 18
0
 def wrapper(*args, **kwargs) -> Response:
     if not user_is_admin():
         return make_api_response(
             403, "This endpoint can only be accessed by admins")
     return fn(*args, **kwargs)
Esempio n. 19
0
 def post(self) -> Response:
     tokens = refresh_token(current_user)
     if tokens:
         return make_api_response(200, content=tokens)
     return make_api_response(500, "Failed to refresh token")
Esempio n. 20
0
 def get(self) -> Response:
     return make_api_response(200,
                              content=dict(_links=get_self_links(api, self),
                                           **settings()))
Esempio n. 21
0
def expired_token_loader(token: dict) -> Response:
    token_type = token["type"]
    return make_api_response(401, f"The {token_type} token has expired")
Esempio n. 22
0
 def get(self, token: str):
     decoded = decode_token(token)["identity"]
     return make_api_response(200, decoded["reason"], content=decoded)
Esempio n. 23
0
def unauthorized_loader(error: str) -> Response:
    return make_api_response(401, error)
Esempio n. 24
0
def user_loader(identity: str):
    return make_api_response(401, f"User {identity} not found")
Esempio n. 25
0
 def post(self) -> Response:
     redis_client.publish("skip", "True")
     return make_api_response(200,
                              "Successfully skipped current playing song")
Esempio n. 26
0
 def post(self, username: str, password: str) -> Response:
     tokens = sign_in(username, password)
     if tokens:
         return make_api_response(200, content=tokens)
     return make_api_response(401, "Invalid credentials")