Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Superadmin Implementation #168

Merged
merged 18 commits into from
Jan 27, 2017
Merged

Superadmin Implementation #168

merged 18 commits into from
Jan 27, 2017

Conversation

BrandonCookeDev
Copy link
Collaborator

This PR implements:

  • Superadmin vs Region Admin privileges
  • New UI on the Admin Functions that shows every user some information and allows them to change password

Cooke, Brandon (bc719c) added 8 commits December 23, 2016 00:05
…ty to user model for checking superadmin. Added DAO functions to get and set the superadmin propery.
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.
…min functions of adding new regions and users.
… page now hides adding region/users fields to everyone but superadmins
@BrandonCookeDev
Copy link
Collaborator Author

BrandonCookeDev commented Dec 24, 2016

I still need to implement a unit test or two before we PR this, but the functionality is there.

Cooke, Brandon (bc719c) added 2 commits December 24, 2016 16:38
@BrandonCookeDev
Copy link
Collaborator Author

Tests written and this PR is ready to be reviewed

Cooke, Brandon (bc719c) added 2 commits December 30, 2016 12:35
… would ensure that superadmins have access to Anything and any function on the website
Copy link
Collaborator

@jschnei jschnei left a comment

Choose a reason for hiding this comment

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

just tested it out a bunch on stage -- looks good!

mostly minor things. the biggest thing is whether we actually want to use an Enum for the admin_levels (and if we do, we should be consistent and use the enum constants everywhere instead of string constants).

in addition to these review comments, i'm also having issues as a superadmin editing default rating criteria for a region that's not in my regions list... probably another check is needed there for superadmins?

@@ -348,7 +349,8 @@ 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'))]
Copy link
Collaborator

Choose a reason for hiding this comment

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

since these guys should come from an Enum, probably it is a good idea to add a choices validator so the ORM will automatically check for this. (take a look at how tournament source types work on line 129).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So I've gone with the String Only method, but I'm not really sure how to validate on the ORM. can you point me in the right direction?

Copy link
Collaborator

Choose a reason for hiding this comment

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

so, take a look at line 129 to see how it's done for tournament source_types (challonge, tio, smashgg).

basically, you should do something like

              ('admin_level', orm.StringField(
                  required=True,
                  validators=[orm.validate_choices(ADMIN_LEVEL_CHOICES)]))

and define a list/tuple ADMIN_LEVEL_CHOICES somewhere.

@@ -371,3 +373,8 @@ class Session(orm.Document):
collection_name = 'sessions'
fields = [('session_id', orm.StringField(required=True)),
('user_id', orm.StringField(required=True))]


class AdminLevels(Enum):
Copy link
Collaborator

Choose a reason for hiding this comment

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

probably makes most sense to move this up to the top, next to the SOURCE_TYPE_CHOICES "enum".

For that matter, I'm still not sure if we gain anything by making this an explicit enum instead of just writing a tuple of choices (as in SOURCE_TYPE_CHOICES) and using the validator. It would make more sense I feel if we were mapping human-readable enum values to non-human readable int values (this is what a lot of enums are for), but since we are mapping them to string values, it feels a bit redundant (also, in server.py, it looks like we actually just use the explicit string constants).

Anyway tl;dr either way (enums/lists of strings) is okay, but if we decide to go the Enum route we should probably also convert SOURCE_TYPE_CHOICES to an Enum for consistency.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Going with the string method so no need for this

@@ -137,6 +144,8 @@ def get_user_from_request(request, dao):


def is_user_admin_for_region(user, region):
if user.admin_level == 'SUPER':
Copy link
Collaborator

Choose a reason for hiding this comment

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

if we want to stick with the Enum method, these 'SUPER's and everything should be M.AdminLevels.SUPERs.

Copy link
Collaborator Author

@BrandonCookeDev BrandonCookeDev Jan 9, 2017

Choose a reason for hiding this comment

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

Going with the String method. so no need to change this

@@ -1176,8 +1209,8 @@ 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 dao.get_is_superadmin(user.id):
Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm if we have the user object, can't we just check if user.admin_level=='SUPER'? This should save a mongo lookup.

@@ -559,7 +559,7 @@ def insert_user(self, user):

# throws invalidRegionsException, which is okay, as this is only used by a
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's remove this comment, it's no longer true with the introduction of the admin dashboard.

@@ -645,6 +665,17 @@ def check_creds_and_get_session_id_or_none(self, username, password):
else:
return None

def check_creds(self, username, password):
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should try not to repeat code (this is basically part of check_creds_and_get_session_id_or_none). maybe let's make check_creds return a user object, and refactor check_creds_and_get_session_id_or_none to call check_creds first?

(this could also be postponed to a later PR, but we should make a note).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OK so. This one is interesting. I tried making its own function via the following:

def get_and_verify_user_by_username(self, username):
        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 user

However, when we run this function, I get problems with this function:

def check_creds_and_get_session_id_or_none(self, username, password):
        user = self.get_and_verify_user_by_username(username)

        # timing oracle on this... good luck
        if verify_password(password, user.salt, user.hashed_password):
            session_id = base64.b64encode(os.urandom(128))
            self.update_session_id_for_user(user.id, session_id)
            return session_id
        else:
            return None

The error I get is that it can't get user.salt for NoneType

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh looking a little closer it might be because it finds no user in the db and then returns None...

Copy link
Collaborator

Choose a reason for hiding this comment

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

i'm confused, it looks there are now 3 functions which repeat a lot of code?:

  1. check_creds(username, password) which returns whether (username, password) are valid credentials.
  2. check_creds_and_get_session_id_or_none(username, password), which checks if (username, password) are valid credentials, and tries to return the current session
  3. get_and_verify_user_by_username, which seems to be a duplicate of get_user_by_username.

Right now 1 and 2 duplicate a lot of the same code. I recommend removing get_and_verify_user_by_username and refactoring 1 and 2 so that check_creds returns a user object (not just a bool), and so that get_session_id calls check_creds to get the user object.

else:
return False

def set_user_superadmin(self, user_id, admin_level):
Copy link
Collaborator

Choose a reason for hiding this comment

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

this can be used for any admin_level, so let's call it something like set_user_admin_level or update_user_admin_level? (it might also make more sense to just make a general update_user function in the same vein as update_player and update_tournament).

@@ -1196,9 +1229,14 @@ def put(self):
uperm = args['new_user_permissions']
uregions = args['new_user_regions']

perm = None
if uperm == 'Super Admin': perm = M.AdminLevels.SUPER
Copy link
Collaborator

Choose a reason for hiding this comment

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

for consistency, let's make the values of the permissions on the frontend match the string values of the enums on the backends (i.e. 'SUPER' and 'REGION')


<h4>Permission Level:</h4>
<select ng-model="postParams.new_user_permissions" id="permissionLevelSelect">
<option>Region Admin</option>
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we put value='REGION' and value='SUPER' here to pass 'REGION' and 'SUPER' to the backend? (not a big deal, but it's nice when values are consistent between frontend and backend)

return False

def set_user_admin_level(self, user_id, admin_level):
if type(admin_level) is not M.AdminLevels:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think M.AdminLevels does not exist anymore, so this should error (is it being tested)?
In any case, the ORM should take care of this check.

@@ -619,6 +618,25 @@ 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
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it is probably better to use get_user_by_id_or_none here instead of rewriting the Mongo queries (so something like user = get_user_by_id_or_none(user_id).

Actually, do we use this function (get_is_super_admin) anywhere else in the code? If not, I think it's fine to remove this function (it is easy enough to just check whether user.admin_level=='SUPER' whenever we have a user object).

else:
return False

def set_user_admin_level(self, user_id, admin_level):
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we actually use this function anywhere else in the code yet? if not, I think it would be fine to just replace it with a simple update_user function, in the same style as update_player and update_region.

def set_user_admin_level(self, user_id, admin_level):
if type(admin_level) is not M.AdminLevels:
raise Exception('Submitted admin level is not of correct type')
if self.users_col.find_one({'_id': user_id}):
Copy link
Collaborator

Choose a reason for hiding this comment

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

right now this way of updating bypasses the ORM completely and won't trigger any of the ORMs validators. the correct way to do this sort of thing is 1. read the user data into a User object (as is done in get_user_by_id_or_none), 2. update whatever properties of the User object, 3. then write the User object back into the database using self.users_col.update({'_id': user.id}, user.dump(context='db')).

@@ -645,6 +665,17 @@ def check_creds_and_get_session_id_or_none(self, username, password):
else:
return None

def check_creds(self, username, password):
Copy link
Collaborator

Choose a reason for hiding this comment

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

i'm confused, it looks there are now 3 functions which repeat a lot of code?:

  1. check_creds(username, password) which returns whether (username, password) are valid credentials.
  2. check_creds_and_get_session_id_or_none(username, password), which checks if (username, password) are valid credentials, and tries to return the current session
  3. get_and_verify_user_by_username, which seems to be a duplicate of get_user_by_username.

Right now 1 and 2 duplicate a lot of the same code. I recommend removing get_and_verify_user_by_username and refactoring 1 and 2 so that check_creds returns a user object (not just a bool), and so that get_session_id calls check_creds to get the user object.

Cooke, Brandon (bc719c) added 2 commits January 23, 2017 01:29
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.
@BrandonCookeDev
Copy link
Collaborator Author

BrandonCookeDev commented Jan 23, 2017

Jon, I made the changes. Notably I deleted the third check_creds thing I had written. I left it in to try to fix whatever bug was in it, but now I have no time.
Also I rewrote the set_admin_level function, and commented it out. Since it isn't implemented yet, and we may want it in the future, I felt this was the best course of action

Additionally, the third function for check_creds (that seemingly duplicate code) I wrote to try to get the user and return that data (the first half of both check_creds functions). However, it wasn't working properly. So I left it in there to fix another day. But instead I just deleted it in the last commit. We can do a refactor on it later; right now I'm just lacking free time.

@jschnei
Copy link
Collaborator

jschnei commented Jan 27, 2017

I fixed a bug with creating new users; I think it works now.

Also, while fixing this I noticed some of the frontend code is probably less angular-y than it should be; there's a lot of places where you're manipulating the DOM directly (I think the angular philosophy is that this should basically always be done through angular directives). Fixing this can probably wait for a later PR though; there is a bunch of frontend refactoring that still needs to be done.

@jschnei jschnei merged commit a5f4def into master Jan 27, 2017
BrandonCookeDev added a commit to BrandonCookeDev/garpr that referenced this pull request Jan 20, 2018
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants