Skip to content

Commit

Permalink
Apply changes
Browse files Browse the repository at this point in the history
  • Loading branch information
vithyze committed Mar 5, 2023
1 parent 8e2adf8 commit b2f0c99
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 39 deletions.
21 changes: 21 additions & 0 deletions config.sample
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ log_level = WARNING
; for testing purposes. Default: on
;mount_api = on

; Require API key for authentication. Default: yes
require_api_key = yes

; Enable the administrative web interface. Default: on
;mount_webui = on

Expand Down Expand Up @@ -87,3 +90,21 @@ default_transcode_target = mp3
;mp3 = audio/mpeg
;ogg = audio/vorbis

[ldap]
; Server URL. Default: none
;url = ldapi://%2Frun%2Fslapd%2Fldapi
;url = ldap://127.0.0.1:389

; Bind credentials. Default: none
;bind_dn = cn=username,dc=example,dc=org
;bind_pw = password

; Base DN. Default: none
;base_dn = ou=Users,dc=example,dc=org

; User filter. The variable '{username}' is used for filtering. Default: none
;user_filter = (&(objectClass=inetOrgperson)(uid={username}))

; Mail attribute. Default: mail
;mail_attr = mail

15 changes: 13 additions & 2 deletions supysonic/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import uuid
from flask import request
from flask import Blueprint
from flask import current_app
from peewee import IntegrityError

from ..db import ClientPrefs, Folder
Expand Down Expand Up @@ -56,10 +57,16 @@ def decode_password(password):

@api.before_request
def authorize():
require_api_key = current_app.config["WEBAPP"]["require_api_key"]

if request.authorization:
user = UserManager.try_auth(
user = UserManager.try_auth_api(
request.authorization.username, request.authorization.password
)
if user is None and not require_api_key:
user = UserManager.try_auth(
request.authorization.username, request.authorization.password
)
if user is not None:
request.user = user
return
Expand All @@ -69,7 +76,11 @@ def authorize():
password = request.values["p"]
password = decode_password(password)

user = UserManager.try_auth(username, password)
user = UserManager.try_auth_api(username, password)
if user is None and not require_api_key:
user = UserManager.try_auth(
request.authorization.username, request.authorization.password
)
if user is None:
raise Unauthorized()

Expand Down
125 changes: 109 additions & 16 deletions supysonic/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import click
import time
import uuid

from click.exceptions import ClickException

Expand Down Expand Up @@ -236,20 +237,20 @@ def user():
def user_list():
"""Lists users."""

click.echo("Name\t\tAdmin\tJukebox\tEmail")
click.echo("----\t\t-----\t-------\t-----")
click.echo("Name\t\tLDAP\tAdmin\tJukebox\tEmail")
click.echo("----\t\t-----\t-----\t-------\t-----")
for u in User.select():
click.echo(
"{: <16}{}\t{}\t{}".format(
u.name, "*" if u.admin else "", "*" if u.jukebox else "", u.mail
"{: <16}{}\t{}\t{}\t{}".format(
u.name, "*" if u.ldap else "", "*" if u.admin else "", "*" if u.jukebox else "", u.mail
)
)


@user.command("add")
@click.argument("name")
@click.password_option("-p", "--password", help="Specifies the user's password")
@click.option("-e", "--email", default="", help="Sets the user's email address")
@click.option("-e", "--email", default=None, help="Sets the user's email address")
def user_add(name, password, email):
"""Adds a new user.
Expand All @@ -262,10 +263,42 @@ def user_add(name, password, email):
raise ClickException(str(e)) from e


@user.group("edit")
def user_edit():
"""User edit commands"""
pass


@user_edit.command("email")
@click.argument("name")
@click.option("-e", "--email", prompt=True, default="", help="Sets the user's email address")
def user_edit_email(name, email):
"""Changes an user's email.
NAME is the name (or login) of the user to edit.
"""

user = User.get(name=name)
if user is None:
raise ClickException("No such user")

if user.ldap:
raise ClickException("Unavailable for LDAP users")

if email == "":
email = None

if user.mail != email:
user.mail = email
user.save()
click.echo(f"Updated user '{name}'")


@user.command("delete")
@click.argument("name")
def user_delete(name):
"""Deletes a user.
"""Deletes an user.
NAME is the name of the user to delete.
"""
Expand Down Expand Up @@ -295,7 +328,7 @@ def _echo_role_change(username, name, value):
help="Grant or revoke jukebox rights",
)
def user_roles(name, admin, jukebox):
"""Enable/disable rights for a user.
"""Enable/disable rights for an user.
NAME is the login of the user to which grant or revoke rights.
"""
Expand All @@ -314,27 +347,31 @@ def user_roles(name, admin, jukebox):
user.save()


@user.command("changepass")
@user_edit.command("password")
@click.argument("name")
@click.password_option("-p", "--password", help="New password")
def user_changepass(name, password):
"""Changes a user's password.
"""Changes an user's password.
NAME is the login of the user to which change the password.
"""

try:
UserManager.change_password2(name, password)
click.echo(f"Successfully changed '{name}' password")
except User.DoesNotExist as e:
raise ClickException(f"User '{name}' does not exist.") from e
user = User.get(name=name)
if user is None:
raise ClickException(f"User '{name}' does not exist.")

if user.ldap:
raise ClickException("Unavailable for LDAP users")

UserManager.change_password2(name, password)
click.echo(f"Successfully changed '{name}' password")

@user.command("rename")

@user_edit.command("username")
@click.argument("name")
@click.argument("newname")
def user_rename(name, newname):
"""Renames a user.
"""Renames an user.
User NAME will then be known as NEWNAME.
"""
Expand All @@ -361,6 +398,62 @@ def user_rename(name, newname):
click.echo(f"User '{name}' renamed to '{newname}'")


@user.group("apikey")
def user_apikey():
"""User API key commands"""
pass


@user_apikey.command("show")
@click.argument("name")
def user_apikey_show(name):
"""Shows the API key of an user.
NAME is the name (or login) of the user to show the API key.
"""

user = User.get(name=name)
if user is None:
raise ClickException(f"User '{name}' does not exist.")

click.echo(f"'{name}' API key: {user.api_key}")


@user_apikey.command("new")
@click.argument("name")
def user_apikey_new(name):
"""Generates a new API key for an user.
NAME is the name (or login) of the user to generate the API key for.
"""

user = User.get(name=name)
if user is None:
raise ClickException(f"User '{name}' does not exist.")

user.api_key = str(uuid.uuid4()).replace("-", "")
user.save()
click.echo(f"Updated '{name}' API key")


@user_apikey.command("delete")
@click.argument("name")
def user_apikey_delete(name):
"""Deletes the API key of an user.
NAME is the name (or login) of the user to delete the API key.
"""

user = User.get(name=name)
if user is None:
raise ClickException(f"User '{name}' does not exist.")

if user.api_key is not None:
user.api_key = None
user.save()
click.echo(f"Deleted '{name}' API key")


def main():
config = IniConfig.from_common_locations()
init_database(config.BASE["database_uri"])
Expand Down
9 changes: 9 additions & 0 deletions supysonic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class DefaultConfig:
"log_level": "WARNING",
"mount_webui": True,
"mount_api": True,
"require_api_key": True,
"index_ignored_prefixes": "El La Le Las Les Los The",
}
DAEMON = {
Expand All @@ -51,6 +52,14 @@ class DefaultConfig:
LASTFM = {"api_key": None, "secret": None}
TRANSCODING = {}
MIMETYPES = {}
LDAP = {
"url": None,
"bind_dn": None,
"bind_pw": None,
"base_dn": None,
"user_filter": None,
"mail_attr": "mail"
}

def __init__(self):
current_config = self
Expand Down
7 changes: 5 additions & 2 deletions supysonic/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,15 @@ class User(_Model):
id = PrimaryKeyField()
name = CharField(64, unique=True)
mail = CharField(null=True)
password = FixedCharField(40)
salt = FixedCharField(6)
password = FixedCharField(40, null=True)
salt = FixedCharField(6, null=True)

ldap = BooleanField(default=False)
admin = BooleanField(default=False)
jukebox = BooleanField(default=False)

api_key = CharField(32, null=True)

lastfm_session = FixedCharField(32, null=True)
lastfm_status = BooleanField(
default=True
Expand Down
Loading

0 comments on commit b2f0c99

Please sign in to comment.