initial commit
This commit is contained in:
0
lemur/users/__init__.py
Normal file
0
lemur/users/__init__.py
Normal file
88
lemur/users/models.py
Normal file
88
lemur/users/models.py
Normal file
@ -0,0 +1,88 @@
|
||||
"""
|
||||
.. module: lemur.users.models
|
||||
:platform: unix
|
||||
:synopsis: This module contains all of the models need to create a user within
|
||||
lemur
|
||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||
:license: Apache, see LICENSE for more details.
|
||||
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime
|
||||
from sqlalchemy.event import listen
|
||||
|
||||
|
||||
from lemur.database import db
|
||||
from lemur.models import roles_users
|
||||
|
||||
from lemur.extensions import bcrypt
|
||||
|
||||
|
||||
def hash_password(mapper, connect, target):
|
||||
"""
|
||||
Helper function that is a listener and hashes passwords before
|
||||
insertion into the database.
|
||||
|
||||
:param mapper:
|
||||
:param connect:
|
||||
:param target:
|
||||
"""
|
||||
target.hash_password()
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
__tablename__ = 'users'
|
||||
id = Column(Integer, primary_key=True)
|
||||
password = Column(String(128))
|
||||
active = Column(Boolean())
|
||||
confirmed_at = Column(DateTime())
|
||||
username = Column(String(255), nullable=False, unique=True)
|
||||
email = Column(String(128), unique=True)
|
||||
profile_picture = Column(String(255))
|
||||
roles = relationship('Role', secondary=roles_users, passive_deletes=True, backref=db.backref('user'), lazy='dynamic')
|
||||
certificates = relationship("Certificate", backref=db.backref('user'), lazy='dynamic')
|
||||
authorities = relationship("Authority", backref=db.backref('user'), lazy='dynamic')
|
||||
|
||||
def check_password(self, password):
|
||||
"""
|
||||
Hash a given password and check it against the stored value
|
||||
to determine it's validity.
|
||||
|
||||
:param password:
|
||||
:return:
|
||||
"""
|
||||
return bcrypt.check_password_hash(self.password, password)
|
||||
|
||||
def hash_password(self):
|
||||
"""
|
||||
Generate the secure hash for the password.
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.password = bcrypt.generate_password_hash(self.password)
|
||||
return self.password
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
"""
|
||||
Determine if the current user has the 'admin' role associated
|
||||
with it.
|
||||
|
||||
:return:
|
||||
"""
|
||||
for role in self.roles:
|
||||
if role.name == 'admin':
|
||||
return True
|
||||
|
||||
def as_dict(self):
|
||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||
|
||||
def serialize(self):
|
||||
blob = self.as_dict()
|
||||
return blob
|
||||
|
||||
|
||||
listen(User, 'before_insert', hash_password)
|
||||
|
||||
|
150
lemur/users/service.py
Normal file
150
lemur/users/service.py
Normal file
@ -0,0 +1,150 @@
|
||||
"""
|
||||
.. module: lemur.users.service
|
||||
:platform: Unix
|
||||
:synopsis: This module contains all of the services level functions used to
|
||||
administer users in Lemur
|
||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from lemur import database
|
||||
from lemur.users.models import User
|
||||
|
||||
|
||||
def create(username, password, email, active, profile_picture, roles):
|
||||
"""
|
||||
Create a new user
|
||||
|
||||
:param username:
|
||||
:param password:
|
||||
:param email:
|
||||
:param active:
|
||||
:param profile_picture:
|
||||
:param roles:
|
||||
:return:
|
||||
"""
|
||||
user = User(
|
||||
password=password,
|
||||
username=username,
|
||||
email=email,
|
||||
active=active,
|
||||
profile_picture=profile_picture,
|
||||
role=roles
|
||||
)
|
||||
user = database.create(user)
|
||||
return database.update(user)
|
||||
|
||||
|
||||
def update(user_id, username, email, active, profile_picture, roles):
|
||||
"""
|
||||
Updates an existing user
|
||||
|
||||
:param user_id:
|
||||
:param username:
|
||||
:param email:
|
||||
:param active:
|
||||
:param profile_picture:
|
||||
:param roles:
|
||||
:return:
|
||||
"""
|
||||
user = get(user_id)
|
||||
user.username = username
|
||||
user.email = email
|
||||
user.active = active
|
||||
user.profile_picture = profile_picture
|
||||
update_roles(user, roles)
|
||||
return database.update(user)
|
||||
|
||||
|
||||
def update_roles(user, roles):
|
||||
"""
|
||||
Replaces the roles with new ones. This will detect
|
||||
when are roles added as well as when there are roles
|
||||
removed.
|
||||
|
||||
:param user:
|
||||
:param roles:
|
||||
"""
|
||||
for ur in roles:
|
||||
for r in roles:
|
||||
if r.id == ur.id:
|
||||
break
|
||||
else:
|
||||
user.roles.remove(r)
|
||||
|
||||
for r in roles:
|
||||
for ur in user.roles:
|
||||
if r.id == ur.id:
|
||||
break
|
||||
else:
|
||||
user.roles.append(r)
|
||||
|
||||
|
||||
def get(user_id):
|
||||
"""
|
||||
Retrieve a user from the database
|
||||
|
||||
:param user_id:
|
||||
:return:
|
||||
"""
|
||||
return database.get(User, user_id)
|
||||
|
||||
|
||||
def get_by_email(email):
|
||||
"""
|
||||
Retrieve a user from the database by their email address
|
||||
|
||||
:param email:
|
||||
:return:
|
||||
"""
|
||||
return database.get(User, email, field='email')
|
||||
|
||||
|
||||
def get_by_username(username):
|
||||
"""
|
||||
Retrieve a user from the database by their username
|
||||
|
||||
:param username:
|
||||
:return:
|
||||
"""
|
||||
return database.get(User, username, field='username')
|
||||
|
||||
|
||||
def get_all():
|
||||
"""
|
||||
Retrieve all users from the database.
|
||||
|
||||
:return:
|
||||
"""
|
||||
query = database.session_query(User)
|
||||
return database.find_all(query, User, {}).all()
|
||||
|
||||
|
||||
def render(args):
|
||||
"""
|
||||
Helper that paginates and filters data when requested
|
||||
through the REST Api
|
||||
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
query = database.session_query(User)
|
||||
|
||||
sort_by = args.pop('sort_by')
|
||||
sort_dir = args.pop('sort_dir')
|
||||
page = args.pop('page')
|
||||
count = args.pop('count')
|
||||
filt = args.pop('filter')
|
||||
|
||||
if filt:
|
||||
terms = filt.split(';')
|
||||
query = database.filter(query, User, terms)
|
||||
|
||||
query = database.find_all(query, User, args)
|
||||
|
||||
if sort_by and sort_dir:
|
||||
query = database.sort(query, User, sort_by, sort_dir)
|
||||
|
||||
return database.paginate(query, page, count)
|
||||
|
||||
|
401
lemur/users/views.py
Normal file
401
lemur/users/views.py
Normal file
@ -0,0 +1,401 @@
|
||||
"""
|
||||
.. module: lemur.user.views
|
||||
:platform: Unix
|
||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from flask import g, Blueprint
|
||||
from flask.ext.restful import reqparse, Api, fields
|
||||
|
||||
from lemur.users import service
|
||||
from lemur.certificates import service as certificate_service
|
||||
from lemur.roles import service as role_service
|
||||
from lemur.auth.service import AuthenticatedResource
|
||||
from lemur.auth.permissions import admin_permission
|
||||
from lemur.common.utils import marshal_items, paginated_parser
|
||||
|
||||
|
||||
mod = Blueprint('users', __name__)
|
||||
api = Api(mod)
|
||||
|
||||
|
||||
FIELDS = {
|
||||
'username': fields.String,
|
||||
'active': fields.Boolean,
|
||||
'email': fields.String,
|
||||
'profileImage': fields.String(attribute='profile_picture'),
|
||||
'id': fields.Integer,
|
||||
}
|
||||
|
||||
|
||||
def roles(values):
|
||||
"""
|
||||
Validate that the passed in roles exist.
|
||||
|
||||
:param values:
|
||||
:return: :raise ValueError:
|
||||
"""
|
||||
rs = []
|
||||
for role in values:
|
||||
r = role_service.get(role['id'])
|
||||
if not r:
|
||||
raise ValueError("Role {0} does not exist".format(role['name']))
|
||||
rs.append(r)
|
||||
return rs
|
||||
|
||||
|
||||
class UsersList(AuthenticatedResource):
|
||||
""" Defines the 'users' endpoint """
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(UsersList, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self):
|
||||
"""
|
||||
.. http:get:: /users
|
||||
|
||||
The current user list
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /users HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 2,
|
||||
"active": True,
|
||||
"email": "user2@example.com",
|
||||
"username": "user2",
|
||||
"profileImage": null
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"active": False,
|
||||
"email": "user1@example.com",
|
||||
"username": "user1",
|
||||
"profileImage": null
|
||||
}
|
||||
]
|
||||
"total": 2
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
:query sortDir: acs or desc
|
||||
:query page: int. default is 1
|
||||
:query filter: key value pair. format is k=v;
|
||||
:query limit: limit number. default is 10
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
parser = paginated_parser.copy()
|
||||
parser.add_argument('owner', type=str, location='args')
|
||||
parser.add_argument('id', type=str, location='args')
|
||||
args = parser.parse_args()
|
||||
return service.render(args)
|
||||
|
||||
@admin_permission.require(http_exception=403)
|
||||
@marshal_items(FIELDS)
|
||||
def post(self):
|
||||
"""
|
||||
.. http:post:: /users
|
||||
|
||||
Creates a new user
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /users HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"username": "user3",
|
||||
"email": "user3@example.com",
|
||||
"active": true,
|
||||
"roles": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 3,
|
||||
"active": True,
|
||||
"email": "user3@example.com,
|
||||
"username": "user3",
|
||||
"profileImage": null
|
||||
}
|
||||
|
||||
:arg username: username for new user
|
||||
:arg email: email address for new user
|
||||
:arg password: password for new user
|
||||
:arg active: boolean, if the user is currently active
|
||||
:arg roles: list, roles that the user should be apart of
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
self.reqparse.add_argument('username', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('email', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('password', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('active', type=bool, default=True, location='json')
|
||||
self.reqparse.add_argument('roles', type=roles, default=[], location='json')
|
||||
|
||||
args = self.reqparse.parse_args()
|
||||
return service.create(args['username'], args['password'], args['email'], args['active'], None, args['roles'])
|
||||
|
||||
|
||||
class Users(AuthenticatedResource):
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(Users, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, user_id):
|
||||
"""
|
||||
.. http:get:: /users/1
|
||||
|
||||
Get a specific user
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /users/1 HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"active": false,
|
||||
"email": "user1@example.com",
|
||||
"username": "user1",
|
||||
"profileImage": null
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
return service.get(user_id)
|
||||
|
||||
@admin_permission.require(http_exception=403)
|
||||
@marshal_items(FIELDS)
|
||||
def put(self, user_id):
|
||||
"""
|
||||
.. http:put:: /users/1
|
||||
|
||||
Update a user
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /users/1 HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"username": "user1",
|
||||
"email": "user1@example.com",
|
||||
"active": false,
|
||||
"roles": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"username": "user1",
|
||||
"email": "user1@example.com",
|
||||
"active": false,
|
||||
"profileImage": null
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
self.reqparse.add_argument('username', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('email', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('active', type=bool, location='json', required=True)
|
||||
self.reqparse.add_argument('roles', type=roles, default=[], location='json', required=True)
|
||||
|
||||
args = self.reqparse.parse_args()
|
||||
return service.update(user_id, args['username'], args['email'], args['active'], None, args['roles'])
|
||||
|
||||
|
||||
class CertificateUsers(AuthenticatedResource):
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(CertificateUsers, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, certificate_id):
|
||||
"""
|
||||
.. http:get:: /certificates/1/creator
|
||||
|
||||
Get a certificate's creator
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /certificates/1/creator HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"active": false,
|
||||
"email": "user1@example.com",
|
||||
"username": "user1",
|
||||
"profileImage": null
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
return certificate_service.get(certificate_id).user
|
||||
|
||||
|
||||
class RoleUsers(AuthenticatedResource):
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(RoleUsers, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, role_id):
|
||||
"""
|
||||
.. http:get:: /roles/1/users
|
||||
|
||||
Get all users associated with a role
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /roles/1/users HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 2,
|
||||
"active": True,
|
||||
"email": "user2@example.com",
|
||||
"username": "user2",
|
||||
"profileImage": null
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"active": False,
|
||||
"email": "user1@example.com",
|
||||
"username": "user1",
|
||||
"profileImage": null
|
||||
}
|
||||
]
|
||||
"total": 2
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
return role_service.get(role_id).users
|
||||
|
||||
|
||||
class Me(AuthenticatedResource):
|
||||
def __init__(self):
|
||||
super(Me, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self):
|
||||
"""
|
||||
.. http:get:: /auth/me
|
||||
|
||||
Get the currently authenticated user
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /auth/me HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"active": false,
|
||||
"email": "user1@example.com",
|
||||
"username": "user1",
|
||||
"profileImage": null
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
return g.current_user.as_dict()
|
||||
|
||||
|
||||
api.add_resource(Me, '/auth/me', endpoint='me')
|
||||
api.add_resource(UsersList, '/users', endpoint='users')
|
||||
api.add_resource(Users, '/users/<int:user_id>', endpoint='user')
|
||||
api.add_resource(CertificateUsers, '/certificates/<int:certificate_id>/creator', endpoint='certificateCreator')
|
||||
api.add_resource(RoleUsers, '/roles/<int:role_id>/users', endpoint='roleUsers')
|
Reference in New Issue
Block a user