Skip to content

Commit

Permalink
Superadmin Implementation (#168)
Browse files Browse the repository at this point in the history
* Added function to sessions service to check super admin. Added property to user model for checking superadmin. Added DAO functions to get and set the superadmin propery.

* Made is_superadmin false by default (new users).
Added a link to the admin console fro dropdown when a user is logged in.
Made Super Admin functions only show up for superadmin users.

* Bug fixes for get_is_superadmin. Added a Superadmin check onto the admin functions of adding new regions and users.

* Adding additional features to admin portal. Change password. List regions.

* Refactored superadmin from T/F booleans into enumerated Strings

* Successfully programmed password change form to work. Admin functions page now hides adding region/users fields to everyone but superadmins

* Added select field for permissions on inserting a new user form

* Bug fix for admin permission levels

* Added unit tests for Changing Password via PUT request, and for testing serer side if a user has SuperAdmin privileges

* Added tests to try adding a region and user with and without superadmin privileges

* Added lines to check SuperAdmin privileges before regular admin. This would ensure that superadmins have access to Anything and any function on the website

* Fixed syntax error

* Refactoring for PR. 80% done.

* PR Fixes. Added admin level validators. Removed Comment not neccessary.

* Changes for Jon.
Rewrote set admin-level. Also commented it out until there's a time we need to use it.
Deleted the third creds_check because it was broken initially. I left it in in case we could figure it out but I have no time anymore.

* fix bug with creating users in admin functions
  • Loading branch information
BrandonCookeDev authored and jschnei committed Jan 27, 2017
1 parent a2b52cb commit a5f4def
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 109 deletions.
38 changes: 33 additions & 5 deletions dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ class DuplicateUsernameException(Exception):
# safe, only used from script
pass


class DuplicateRegionException(Exception):
pass


class InvalidNameException(Exception):
# safe only used in dead code
pass
Expand Down Expand Up @@ -566,9 +568,7 @@ def insert_user(self, user):

return self.users_col.insert(user.dump(context='db'))

# throws invalidRegionsException, which is okay, as this is only used by a
# script
def create_user(self, username, password, regions):
def create_user(self, username, password, regions, perm='REGION'):
valid_regions = [
region.id for region in Dao.get_all_regions(self.mongo_client)]

Expand All @@ -577,15 +577,16 @@ def create_user(self, username, password, regions):
print 'Invalid region name:', region

regions = [region for region in regions if region in valid_regions]
if len(regions) == 0:
if len(regions) == 0 and perm == 'REGION':
raise InvalidRegionsException("No valid region for new user")

salt, hashed_password = gen_password(password)
the_user = M.User(id="userid--" + username,
admin_regions=regions,
username=username,
salt=salt,
hashed_password=hashed_password)
hashed_password=hashed_password,
admin_level=perm)

return self.insert_user(the_user)

Expand Down Expand Up @@ -628,6 +629,24 @@ def get_user_by_session_id_or_none(self, session_id):
def get_user_by_region(self, regions):
pass

def get_is_superadmin(self, user_id):
user = None
if self.users_col.find_one({'_id': user_id}):
user = self.get_user_by_id_or_none(user_id)
return user.admin_level == 'SUPER'
else:
return False

'''
def set_user_admin_level(self, user_id, admin_level):
if type(admin_level) not in M.ADMIN_LEVEL_CHOICES:
raise Exception('Submitted admin level is not of correct type')
else:
user = self.get_user_by_id_or_none(user_id)
user.admin_level = admin_level
self.users_col.update({'_id': user.id}, user.dump(context='db'))
'''

#### FOR INTERNAL USE ONLY ####
#XXX: this method must NEVER be publicly routeable, or you have session-hijacking
def get_session_id_by_user_or_none(self, User):
Expand All @@ -654,6 +673,15 @@ def check_creds_and_get_session_id_or_none(self, username, password):
else:
return None

def check_creds(self, username, password):
result = self.users_col.find({"username": username})
if result.count() == 0:
return None
assert result.count() == 1, "WE HAVE DUPLICATE USERNAMES IN THE DB"
user = M.User.load(result[0], context='db')
assert user, "mongo has stopped being consistent, abort ship"
return verify_password(password, user.salt, user.hashed_password)

def update_session_id_for_user(self, user_id, session_id):
# lets force people to have only one session at a time
self.sessions_col.remove({"user_id": user_id})
Expand Down
7 changes: 5 additions & 2 deletions model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import orm

SOURCE_TYPE_CHOICES = ('tio', 'challonge', 'smashgg', 'other')

ADMIN_LEVEL_CHOICES = ('REGION', 'SUPER')
# Embedded documents

class AliasMapping(orm.Document):
Expand Down Expand Up @@ -353,7 +353,10 @@ class User(orm.Document):
('username', orm.StringField(required=True)),
('salt', orm.StringField(required=True)),
('hashed_password', orm.StringField(required=True)),
('admin_regions', orm.ListField(orm.StringField()))]
('admin_regions', orm.ListField(orm.StringField())),
('admin_level', orm.StringField(required=True, default='REGION',
validators=[orm.validate_choices(ADMIN_LEVEL_CHOICES)])
)]


class Merge(orm.Document):
Expand Down
54 changes: 47 additions & 7 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
TYPEAHEAD_PLAYER_LIMIT = 20
BASE_REGION = 'newjersey'


# parse config file
config = Config()

Expand Down Expand Up @@ -121,6 +122,10 @@
admin_functions_parser.add_argument('new_user_permissions', location='json', type=str)
admin_functions_parser.add_argument('new_user_regions', location='json', type=list)

user_parser = reqparse.RequestParser()
user_parser.add_argument('old_pass', location='json', type=str)
user_parser.add_argument('new_pass', location='json', type=str)

#TODO: major refactor to move auth code to a decorator


Expand All @@ -140,6 +145,8 @@ def get_user_from_request(request, dao):


def is_user_admin_for_region(user, region):
if user.admin_level == 'SUPER':
return True
if not region:
return False
if not user.admin_regions:
Expand All @@ -153,7 +160,9 @@ def is_user_admin_for_regions(user, regions):
'''
returns true is user is an admin for ANY of the regions
'''
if len(set(regions).intersection(user.admin_regions)) == 0:
if user.admin_level == 'SUPER':
return True
elif len(set(regions).intersection(user.admin_regions)) == 0:
return False
else:
return True
Expand Down Expand Up @@ -1201,6 +1210,28 @@ def get(self):

return return_dict

class UserResource(restful.Resource):
def put(self):
dao = Dao(None, mongo_client=mongo_client)

if not dao:
return 'Dao not found', 404
user = get_user_from_request(request, dao)
if not user:
return 'Permission denied', 403

args = user_parser.parse_args()
old_pass = args['old_pass']
new_pass = args['new_pass']

try:
if dao.check_creds(user.username, old_pass):
dao.change_passwd(user.username, new_pass)
return 200
else: return 'Bad password', 403
except Exception as ex:
return 'Password change not successful', 400


class AdminFunctionsResource(restful.Resource):
def get(self):
Expand All @@ -1214,17 +1245,16 @@ def put(self):
user = get_user_from_request(request, dao)
if not user:
return 'Permission denied', 403
#if not is_user_admin_for_region(user, region='*'):
# return 'Permission denied', 403
if not user.admin_level == 'SUPER':
return 'Permission denied', 403

args = admin_functions_parser.parse_args()

function_type = args['function_type']
if function_type == 'region':
region_name = args['new_region']

#Execute region addition
config = Config()
# Execute region addition
if dao.create_region(region_name):
print("region created:" + region_name)

Expand All @@ -1234,10 +1264,18 @@ def put(self):
uperm = args['new_user_permissions']
uregions = args['new_user_regions']

#Execute user addition
if uperm not in M.ADMIN_LEVEL_CHOICES:
return 'Invalid permission selection!', 403

# Execute user addition
dao = Dao(None, mongo_client)
if dao.create_user(uname, upass, uregions):
try:
dao.create_user(uname, upass, uregions, uperm)
print("user created:" + uname)
except Exception as e:
print e
return 'Error creating user', 400


@api.representation('text/plain')
class LoaderIOTokenResource(restful.Resource):
Expand Down Expand Up @@ -1311,6 +1349,8 @@ def add_cors(resp):

api.add_resource(SessionResource, '/users/session')

api.add_resource(UserResource, '/user')

api.add_resource(LoaderIOTokenResource,
'/{}/'.format(config.get_loaderio_token()))

Expand Down
39 changes: 33 additions & 6 deletions test/test_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ def setUp(self):
admin_regions=self.user_admin_regions_1,
username='user1',
salt='nacl',
hashed_password='browns')
hashed_password='browns',
admin_level='REGION')

self.user_id_2 = 'asdfasdf'
self.user_full_name_2 = 'Full Name'
Expand All @@ -202,9 +203,22 @@ def setUp(self):
admin_regions=self.user_admin_regions_2,
username=self.user_full_name_2,
salt='nacl',
hashed_password='browns')

self.users = [self.user_1, self.user_2]
hashed_password='browns',
admin_level='REGION')

self.superadmin_id = '123456'
self.superadmin_full_name = 'superadmin'
self.superadmin_admin_regions = []
self.superadmin = User(
id=self.superadmin_id,
user_admin_regions=self.superadmin_admin_regions,
username=self.superadmin_full_name,
salt='nacl',
hashed_password='browns',
admin_level='SUPER'
)

self.users = [self.user_1, self.user_2, self.superadmin]

self.region_1 = Region(id='norcal', display_name='Norcal')
self.region_2 = Region(id='texas', display_name='Texas')
Expand Down Expand Up @@ -233,6 +247,8 @@ def setUp(self):
for user in self.users:
self.norcal_dao.insert_user(user)



def tearDown(self):
self.mongo_client.drop_database(DATABASE_NAME)

Expand Down Expand Up @@ -785,7 +801,7 @@ def test_get_latest_ranking(self):

def test_get_all_users(self):
users = self.norcal_dao.get_all_users()
self.assertEquals(len(users), 2)
self.assertEquals(len(users), 3)

user = users[0]
self.assertEquals(user.id, self.user_id_1)
Expand All @@ -797,6 +813,11 @@ def test_get_all_users(self):
self.assertEquals(user.username, self.user_full_name_2)
self.assertEquals(user.admin_regions, self.user_admin_regions_2)

user = users[2]
self.assertEqual(user.id, self.superadmin_id)
self.assertEqual(user.username, self.superadmin_full_name)
self.assertEqual(user.admin_regions, self.superadmin_admin_regions)

def test_create_user(self):
username = 'abra'
password = 'cadabra'
Expand All @@ -805,7 +826,7 @@ def test_create_user(self):
self.norcal_dao.create_user(username, password, regions)

users = self.norcal_dao.get_all_users()
self.assertEquals(len(users), 3)
self.assertEquals(len(users), 4)

user = users[-1]
self.assertEquals(user.username, username)
Expand Down Expand Up @@ -938,3 +959,9 @@ def test_get_nonexistent_merge(self):
dao = self.norcal_dao
self.assertIsNone(dao.get_merge(
ObjectId("420f53650181b84aaaa01051"))) # mlg1337noscope

def test_get_is_superadmin(self):
dao = self.norcal_dao
self.assertEqual(dao.get_is_superadmin(self.user_1.id), False)
self.assertEqual(dao.get_is_superadmin(self.user_2.id), False)
self.assertEqual(dao.get_is_superadmin(self.superadmin.id), True)
7 changes: 5 additions & 2 deletions test/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,18 +616,21 @@ def setUp(self):
self.username = 'ASDF fdsa'
self.salt = 'nacl'
self.hashed_password = 'browns'
self.admin_level = 'REGION'
self.user = User(id=self.id,
admin_regions=self.admin_regions,
username=self.username,
salt=self.salt,
hashed_password=self.hashed_password)
hashed_password=self.hashed_password,
admin_level=self.admin_level)

self.user_json_dict = {
'_id': self.id,
'username': self.username,
'admin_regions': self.admin_regions,
'salt': self.salt,
'hashed_password': self.hashed_password
'hashed_password': self.hashed_password,
'admin_level': self.admin_level
}

def test_dump(self):
Expand Down
Loading

0 comments on commit a5f4def

Please sign in to comment.