Skip to content
This repository has been archived by the owner on Feb 20, 2024. It is now read-only.

Add more user management #115

Merged
merged 23 commits into from
Jun 11, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c018471
Tweak endpoints for creating users
nginyc Jun 5, 2019
f7a238e
Exit upon error in pulling images
nginyc Jun 5, 2019
80f4a87
Update version
nginyc Jun 5, 2019
ce37eac
Add `get_users` and `delete_user`; disallow admins from managing othe…
nginyc Jun 6, 2019
9e6edd0
Disallow deleting self
nginyc Jun 6, 2019
0c40985
Disallow creating invalid user type; remove unused 'USER' user type
nginyc Jun 6, 2019
dedfe3a
Move index doc & doc config file into `docs/`
nginyc Jun 6, 2019
5ec7eaa
Don't gitignore files in `docs/`
nginyc Jun 6, 2019
d2e7673
Add users management examples to docs
nginyc Jun 6, 2019
64710af
Allow passing of `CSV_FILE_PATH`
nginyc Jun 6, 2019
e557e6e
Abstract user type validation
nginyc Jun 7, 2019
4e226af
Move user type validation to database file
nginyc Jun 7, 2019
69f6889
Shorten code with `datetime`
nginyc Jun 7, 2019
44877e4
Remove unused `tokens` endpoint on advisor
nginyc Jun 7, 2019
6926e6d
Change user deletion to ban; expire login token
nginyc Jun 7, 2019
bad4c3c
Update docs on banning user
nginyc Jun 7, 2019
163b726
Merge changes from `v0.1.0` branch
nginyc Jun 7, 2019
b847a20
Fix bug where database is not dumped
nginyc Jun 7, 2019
d629536
Merge branch 'delete-users' of https://github.com/nginyc/rafiki into …
nginyc Jun 7, 2019
cfc90af
Fix echo statement
nginyc Jun 7, 2019
5d198fd
Remove `create_users_with_csv`
nginyc Jun 7, 2019
9e9a371
Throw error upon banning banned user
nginyc Jun 7, 2019
3a21ddd
Add doc on `sudo -E`
nginyc Jun 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .env.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Core external configuration for Rafiki
export DOCKER_NETWORK=rafiki
export DOCKER_SWARM_ADVERTISE_ADDR=127.0.0.1
export RAFIKI_VERSION=0.0.9
export RAFIKI_VERSION=0.1.0
export RAFIKI_ADDR=127.0.0.1
export ADMIN_EXT_PORT=3000
export ADMIN_WEB_EXT_PORT=3001
Expand Down Expand Up @@ -45,8 +45,4 @@ export IMAGE_POSTGRES=postgres:10.5-alpine
export IMAGE_REDIS=redis:5.0.3-alpine3.8

# Utility configuration
export PYTHONPATH=$PWD # Ensures that `rafiki` module can be imported at project root

# Set alias for correct PIP & python
alias pip='pip3.6'
alias python='python3.6'
export PYTHONPATH=$PWD # Ensures that `rafiki` module can be imported at project root
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ __pycache__/

# Sphinx documentation
.doctrees/
docs/*
docs/**/
!docs/src

# Datasets
Expand Down
File renamed without changes.
7 changes: 3 additions & 4 deletions index.rst → docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Index
.. toctree::
:maxdepth: 2

docs/src/user/index.rst
docs/src/dev/index.rst
docs/src/python/index.rst
src/user/index.rst
src/dev/index.rst
src/python/index.rst

What is Rafiki?
--------------------------------------------------------------------
Expand All @@ -33,7 +33,6 @@ For *Model Developers*, they can:
- Contribute to Rafiki's pool of model templates



Check out :ref:`quick-setup` to deploy/develop Rafiki on your machine, and/or :ref:`quick-start` to use a deployed instance of Rafiki.

Issues
Expand Down
61 changes: 55 additions & 6 deletions docs/src/user/quickstart-admins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,60 @@ Examples:
user_type='APP_DEVELOPER'
)

client.create_user(
email='model_developer@rafiki',
password='rafiki',
user_type='MODEL_DEVELOPER'
)

.. seealso:: :meth:`rafiki.client.Client.create_user`


Creating multiple users with a CSV file
--------------------------------------------------------------------

Example:

.. code-block:: python

client.create_users_with_csv('examples/scripts/users.csv')


.. seealso:: :meth:`rafiki.client.Client.create_users_with_csv`


Listing all users
--------------------------------------------------------------------

Example:

.. code-block:: python

client.get_users()


.. code-block:: python

[{'email': 'superadmin@rafiki',
'id': 'c815fa08-ce06-467d-941b-afc27684d092',
'user_type': 'SUPERADMIN'},
{'email': 'admin@rafiki',
'id': 'cb2c0d61-acd3-4b65-a5a7-d78aa5648283',
'user_type': 'ADMIN'},
{'email': 'model_developer@rafiki',
'id': 'bfe58183-9c69-4fbd-a7b3-3fdc267b3290',
'user_type': 'MODEL_DEVELOPER'},
{'email': 'app_developer@rafiki',
'id': '958a7d65-aa1d-437f-858e-8837bb3ecf32',
'user_type': 'APP_DEVELOPER'}]


.. seealso:: :meth:`rafiki.client.Client.get_users`


Deleting a user
--------------------------------------------------------------------

Example:

.. code-block:: python

client.delete_user('app_developer@rafiki')


.. seealso:: :meth:`rafiki.client.Client.create_user`
.. seealso:: :meth:`rafiki.client.Client.delete_user`
7 changes: 4 additions & 3 deletions examples/scripts/seed_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from rafiki.client import Client
from rafiki.config import SUPERADMIN_EMAIL, SUPERADMIN_PASSWORD

def seed_users(client):
users = client.create_users('examples/seeds/users.csv')
def seed_users(client, csv_file_path):
users = client.create_users_with_csv(csv_file_path)
pprint.pprint(users)

if __name__ == '__main__':
Expand All @@ -14,9 +14,10 @@ def seed_users(client):
admin_web_port = int(os.environ.get('ADMIN_WEB_EXT_PORT', 3001))
user_email = os.environ.get('USER_EMAIL', SUPERADMIN_EMAIL)
user_password = os.environ.get('USER_PASSWORD', SUPERADMIN_PASSWORD)
csv_file_path = os.environ.get('CSV_FILE_PATH', 'examples/scripts/users.csv')

# Initialize client
client = Client(admin_host=rafiki_host, admin_port=admin_port)
client.login(email=user_email, password=user_password)

seed_users(client)
seed_users(client, csv_file_path)
4 changes: 4 additions & 0 deletions examples/scripts/users.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
EMAIL,PASSWORD,USER_TYPE
admin@rafiki,rafiki,ADMIN
model_developer@rafiki,rafiki,MODEL_DEVELOPER
app_developer@rafiki,rafiki,APP_DEVELOPER
7 changes: 0 additions & 7 deletions examples/seeds/users.csv

This file was deleted.

60 changes: 53 additions & 7 deletions rafiki/admin/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __init__(self, db=None, container_manager=None):

def seed(self):
with self._db:
self._seed_users()
self._seed_superadmin()

####################################
# Users
Expand All @@ -65,10 +65,12 @@ def authenticate_user(self, email, password):
def create_user(self, email, password, user_type):
user = self._create_user(email, password, user_type)
return {
'id': user.id
'id': user.id,
'email': user.email,
'user_type': user.user_type
}

def create_users(self, csv_file_bytes):
def create_users_with_csv(self, csv_file_bytes):
temp_csv_file = '{}.csv'.format(str(uuid.uuid4()))

# Temporarily save the csv file to disk
Expand All @@ -80,9 +82,39 @@ def create_users(self, csv_file_bytes):
reader = csv.DictReader(f)
reader.fieldnames = [name.lower() for name in reader.fieldnames]
for row in reader:
user = self._create_user(row['email'], row['password'], row['user_type'])
users.append(user)
email = row['email']
password = row['password']
user_type = row['user_type']
try:
user = self._create_user(email, password, user_type)
users.append(user)
except Exception as e:
logger.info('Failed to create user `{}` due to {}'.format(email, e))

os.remove(temp_csv_file)

return [
{
'id': user.id,
'email': user.email,
'user_type': user.user_type
}
for user in users
]

def get_user_by_email(self, email):
user = self._db.get_user_by_email(email)
if user is None:
return None

return {
'id': user.id,
'email': user.email,
'user_type': user.user_type
}

def get_users(self):
users = self._db.get_users()
return [
{
'id': user.id,
Expand All @@ -92,6 +124,20 @@ def create_users(self, csv_file_bytes):
for user in users
]

def delete_user(self, email):
user = self._db.get_user_by_email(email)
if user is None:
raise InvalidUserError()

self._db.delete_users([user])
self._db.commit()

return {
'id': user.id,
'email': user.email,
'user_type': user.user_type
}

####################################
# Train Job
####################################
Expand Down Expand Up @@ -580,8 +626,8 @@ def get_models_of_task(self, user_id, task):
# Private / Users
####################################

def _seed_users(self):
logger.info('Seeding users...')
def _seed_superadmin(self):
logger.info('Seeding superadmin...')

# Seed superadmin
try:
Expand Down
51 changes: 43 additions & 8 deletions rafiki/admin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import json

from rafiki.constants import UserType
from rafiki.utils.auth import generate_token, decode_token, auth
from rafiki.utils.auth import generate_token, decode_token, auth, UnauthorizedError

from .admin import Admin

Expand All @@ -20,27 +20,62 @@ def index():
# Users
####################################

@app.route('/user', methods=['POST'])
@app.route('/users/csv', methods=['POST'])
@auth([UserType.ADMIN])
def create_users_with_csv(auth):
admin = get_admin()
params = get_request_params()

# Expect csv file as bytes
csv_file_bytes = request.files['csv_file_bytes'].read()
params['csv_file_bytes'] = csv_file_bytes

with admin:
return jsonify(admin.create_users_with_csv(**params))

@app.route('/users', methods=['POST'])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not common to create users using a csv file. Typically, we add users one by one. But we need to get all users for the front-end.
Have you compared users vs user for the operations against a single user? Both are fine, but we need to be consistent with other APIs. E.g., model vs models, trial vs trials.

Copy link
Owner Author

@nginyc nginyc Jun 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. I will be removing this API

@auth([UserType.ADMIN])
def create_user(auth):
admin = get_admin()
params = get_request_params()

# Only superadmins can create admins
if auth['user_type'] != UserType.SUPERADMIN and \
params['user_type'] in [UserType.ADMIN, UserType.SUPERADMIN]:
raise UnauthorizedError()

with admin:
return jsonify(admin.create_user(**params))

@app.route('/users', methods=['POST'])
@app.route('/users', methods=['GET'])
@auth([UserType.ADMIN])
def create_users(auth):
def get_users(auth):
admin = get_admin()
params = get_request_params()

# Expect csv file as bytes
csv_file_bytes = request.files['csv_file_bytes'].read()
params['csv_file_bytes'] = csv_file_bytes
with admin:
return jsonify(admin.get_users(**params))

@app.route('/users', methods=['DELETE'])
@auth([UserType.ADMIN])
def delete_user(auth):
admin = get_admin()
params = get_request_params()

with admin:
return jsonify(admin.create_users(**params))
user = admin.get_user_by_email(params['email'])

if user is not None:
# Only superadmins can delete admins
if auth['user_type'] != UserType.SUPERADMIN and \
user['user_type'] in [UserType.ADMIN, UserType.SUPERADMIN]:
raise UnauthorizedError()

# Cannot delete yourself
if auth['user_id'] == user['id']:
raise UnauthorizedError()

return jsonify(admin.delete_user(**params))

@app.route('/tokens', methods=['POST'])
def generate_user_token():
Expand Down
Loading