├── .gitignore ├── .noserc ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.rst ├── auth ├── CAS │ ├── REST │ │ ├── __init__.py │ │ ├── client.py │ │ └── service.py │ ├── __init__.py │ ├── authorization.py │ └── models │ │ ├── __init__.py │ │ └── db.py ├── __init__.py └── cmd │ ├── __init__.py │ └── server.py ├── docs ├── API_Usage_Teminal.gif └── index.rst ├── requirements.txt ├── setup.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /.noserc: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | verbosity=3 3 | with-doctest=1 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | services: 3 | - mongodb 4 | before_script: 5 | - sleep 15 6 | script: coverage run --source=auth -m pytest -vs tests.py 7 | language: python 8 | python: 9 | - 2.7 10 | - pypy 11 | - 3.3 12 | - 3.4 13 | - 3.5 14 | - pypy3 15 | 16 | before_install: 17 | - pip install codecov 18 | - pip install -r dev_requirements.txt 19 | after_success: 20 | - coverage report -m 21 | - codecov 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:latest 2 | MAINTAINER Farsheed Ashouri 3 | WORKDIR /data 4 | 5 | RUN pip install auth 6 | 7 | 8 | EXPOSE 4000 9 | ENTRYPOINT exec gunicorn auth:api -k eventlet -b 0.0.0.0:4000 -w 8 -t 10 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Farsheed Ashouri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Auth | Authorization for Humans 3 | ==================================== 4 | 5 | RESTful, Simple Authorization system with ZERO configuration. 6 | 7 | .. image:: https://badge.fury.io/py/auth.svg 8 | :target: https://badge.fury.io/py/auth 9 | 10 | .. image:: https://img.shields.io/pypi/dm/auth.svg 11 | :target: https://pypi.python.org/pypi/auth 12 | 13 | 14 | 15 | 16 | .. image:: https://api.travis-ci.org/ourway/auth.svg 17 | :target: https://travis-ci.org/ourway/auth 18 | 19 | .. image:: https://codecov.io/github/ourway/auth/coverage.svg?branch=master 20 | :target: https://codecov.io/github/ourway/auth?branch=master 21 | 22 | 23 | 24 | 25 | *************** 26 | What is Auth? 27 | *************** 28 | Auth is a module that makes authorization simple and also scalable and powerful. It also has a beautiful RESTful API for use in micro-service architectures and platforms. It is originally desinged to use in Appido, a scalable media market in Iran. 29 | 30 | It supports Python2.6+ and if you have a mongodb backbone, you need ZERO configurations steps. Just type ``auth-server`` and press enter! 31 | 32 | I use Travis and Codecov to keep myself honest. 33 | 34 | ******************* 35 | requirements 36 | ******************* 37 | 38 | You need to access to **mongodb**. If you are using a remote mongodb, provide these environment variables: 39 | 40 | ``MONGO_HOST`` and ``MONGO_PORT`` 41 | 42 | 43 | ******************* 44 | Installation 45 | ******************* 46 | 47 | .. code:: Bash 48 | 49 | pip install auth 50 | 51 | 52 | ******************* 53 | Show me an example 54 | ******************* 55 | ok, lets image you have two users, **Jack** and **Sara**. Sara can cook and Jack can dance. Both can laugh. 56 | 57 | You also need to choose a secret key for your application. Because you may want to use Auth in various tools and each must have a secret key for seperating their scope. 58 | 59 | .. code:: Python 60 | 61 | my_secret_key = "pleaSeDoN0tKillMyC_at" 62 | from auth import Authorization 63 | cas = Authorization(my_secret_key) 64 | 65 | Now, Lets add 3 groups, Cookers, Dancers and Laughers. Remember that groups are Roles. So when we create a group, indeed we create a role: 66 | 67 | .. code:: Python 68 | 69 | cas.add_group('cookers') 70 | cas.add_group('dancers') 71 | cas.add_group('laughers') 72 | 73 | 74 | Ok, great. You have 3 groups and you need to authorize them to do special things. 75 | 76 | .. code:: Python 77 | 78 | cas.add_permission('cookers', 'cook') 79 | cas.add_permission('dancers', 'dance') 80 | cas.add_permission('laughers', 'laugh') 81 | 82 | 83 | Good. You let cookers to cook and dancers to dance etc... 84 | The final part is to set memberships for Sara and Jack: 85 | 86 | .. code:: Python 87 | 88 | cas.add_membership('sara', 'cookers') 89 | cas.add_membership('sara', 'laughers') 90 | cas.add_membership('jack', 'dancers') 91 | cas.add_membership('jack', 'laughers') 92 | 93 | 94 | 95 | That's all we need. Now lets ensure that jack can dance: 96 | 97 | .. code:: Python 98 | 99 | if cas.user_has_permission('jack', 'dance'): 100 | print('YES!!! Jack can dance.') 101 | 102 | 103 | 104 | ********************** 105 | Authorization Methods 106 | ********************** 107 | 108 | use pydoc to see all methods: 109 | 110 | .. code:: Bash 111 | 112 | pydoc auth.Authorization 113 | 114 | 115 | ******************* 116 | RESTful API 117 | ******************* 118 | Lets run the server on port 4000: 119 | 120 | .. code:: Python 121 | 122 | from auth import api, serve 123 | serve('localhost', 4000, api) 124 | 125 | Or, from version 0.1.2+ you can use this command: 126 | 127 | .. code:: Bash 128 | 129 | auth-server 130 | 131 | 132 | Simple! Authorization server is ready to use. 133 | 134 | .. image:: https://raw.githubusercontent.com/ourway/auth/master/docs/API_Usage_Teminal.gif 135 | 136 | 137 | You can use it via simple curl or using mighty Requests module. So in you remote application, you can do something like this: 138 | 139 | .. code:: Python 140 | 141 | import requests 142 | secret_key = "pleaSeDoN0tKillMyC_at" 143 | auth_api = "http://127.0.0.1:4000/api" 144 | 145 | 146 | Lets create admin group: 147 | 148 | .. code:: Python 149 | 150 | requests.post(auth_api+'/role/'+secret_key+'/admin') 151 | 152 | 153 | And lets make Jack an admin: 154 | 155 | .. code:: Python 156 | 157 | requests.post(auth_api+'/permission/'+secret_key+'/jack/admin') 158 | 159 | And finally let's check if Sara still can cook: 160 | 161 | .. code:: Python 162 | 163 | requests.get(auth_api+'/has_permission/'+secret_key+'/sara/cook') 164 | 165 | 166 | 167 | ******************** 168 | RESTful API helpers 169 | ******************** 170 | auth comes with a helper class that makes your life easy. 171 | 172 | .. code:: Python 173 | 174 | from auth.client import Client 175 | service = Client('srv201', 'http://192.168.99.100:4000') 176 | print(service) 177 | service.get_roles() 178 | service.add_role(role='admin') 179 | 180 | 181 | ******************* 182 | API Methods 183 | ******************* 184 | 185 | 186 | .. code:: Bash 187 | 188 | pydoc auth.CAS.REST.service 189 | 190 | 191 | 192 | 193 | - ``/ping`` [GET] 194 | 195 | 196 | Ping API, useful for your monitoring tools 197 | 198 | 199 | - ``/api/membership/{KEY}/{user}/{role}`` [GET/POST/DELETE] 200 | 201 | Adding, removing and getting membership information. 202 | 203 | 204 | - ``/api/permission/{KEY}/{role}/{name}`` [GET/POST/DELETE] 205 | 206 | Adding, removing and getting permissions 207 | 208 | 209 | - ``/api/has_permission/{KEY}/{user}/{name}`` [GET] 210 | 211 | Getting user permission info 212 | 213 | 214 | - ``/api/role/{KEY}/{role}`` [GET/POST/DELETE] 215 | 216 | Adding, removing and getting roles 217 | 218 | 219 | - ``/api/which_roles_can/{KEY}/{name}`` [GET] 220 | 221 | For example: Which roles can send_mail? 222 | 223 | 224 | - ``/api/which_users_can/{KEY}/{name}`` [GET] 225 | 226 | For example: Which users can send_mail? 227 | 228 | 229 | - ``/api/user_permissions/{KEY}/{user}`` [GET] 230 | 231 | Get all permissions that a user has 232 | 233 | - ``/api/role_permissions/{KEY}/{role}`` [GET] 234 | 235 | Get all permissions that a role has 236 | 237 | 238 | - ``/api/user_roles/{KEY}/{user}`` [GET] 239 | 240 | Get roles that user assinged to 241 | 242 | - ``/api/roles/{KEY}`` [GET] 243 | 244 | Get all available roles 245 | 246 | 247 | ******************* 248 | Deployment 249 | ******************* 250 | 251 | Deploying Auth module in production environment is easy: 252 | 253 | 254 | .. code:: Bash 255 | 256 | gunicorn auth:api 257 | 258 | 259 | 260 | 261 | ******************* 262 | Dockerizing 263 | ******************* 264 | 265 | It's simple: 266 | 267 | .. code:: Bash 268 | 269 | docker build -t python/auth-server https://raw.githubusercontent.com/ourway/auth/master/Dockerfile 270 | docker run --name=auth -e MONGO_HOST='192.168.99.100' -p 4000:4000 -d --restart=always --link=mongodb-server python/auth-server 271 | 272 | 273 | 274 | ******************* 275 | Copyright 276 | ******************* 277 | 278 | - Farsheed Ashouri `@ `_ 279 | 280 | 281 | ******************* 282 | Documentation 283 | ******************* 284 | Feel free to dig into source code. If you think you can improve the documentation, please do so and send me a pull request. 285 | 286 | ************************ 287 | Unit Tests and Coverage 288 | ************************ 289 | I am trying to add tests as much as I can, but still there are areas that need improvement. 290 | 291 | 292 | ********** 293 | To DO 294 | ********** 295 | - Add Authentication features 296 | - Improve Code Coverage 297 | -------------------------------------------------------------------------------- /auth/CAS/REST/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ourway/auth/4d99713262dc16671c35497d0388dfbb3f97c9bb/auth/CAS/REST/__init__.py -------------------------------------------------------------------------------- /auth/CAS/REST/client.py: -------------------------------------------------------------------------------- 1 | 2 | __author__ = 'Farsheed Ashouri' 3 | 4 | import requests 5 | from requests.exceptions import ConnectionError 6 | import json 7 | import re 8 | 9 | try: 10 | import ujson as json 11 | except ImportError: 12 | pass 13 | 14 | 15 | 16 | services = { 17 | '/ping':['get'], 18 | '/api/membership/{client}/{user}/{group}':['post', 'delete', 'get'], 19 | '/api/permission/{client}/{group}/{name}':['post', 'delete', 'get'], 20 | '/api/has_permission/{client}/{user}/{name}':['get'], 21 | '/api/user_permissions/{client}/{user}' :['get'], 22 | '/api/role_permissions/{client}/{role}':['get'], 23 | '/api/user_roles/{client}/{user}':['get'], 24 | '/api/members/{client}/{role}':['get'], 25 | '/api/role/{client}/{role}':['post', 'delete'], 26 | '/api/roles/{client}':['get'], 27 | '/api/which_roles_can/{client}/{name}':['get'], 28 | '/api/which_users_can/{client}/{name}':['get'], 29 | } 30 | 31 | translate = { 'get':'get', 'post':'add', 'delete':'remove' } 32 | 33 | 34 | def connect(url, method='get'): 35 | func = requests.get 36 | if method=='post': 37 | func = requests.post 38 | elif method=='delete': 39 | func = requests.delete 40 | try: 41 | r = func(url) 42 | return r 43 | except ConnectionError: 44 | raise ConnectionError('Service Down') 45 | 46 | 47 | 48 | def connection_factory(cls, url, method): 49 | def closure(*args, **kw): 50 | attrs = re.findall(re.compile(r'{([\w]+)'),url) 51 | kw['client'] = cls.api_key 52 | try: 53 | assert set(attrs) == set(kw.keys()) 54 | except AssertionError: 55 | attrs.remove('client') 56 | raise AssertionError('I need %s.' % set(attrs)) 57 | link = cls.service_url + url.format(**kw) 58 | r = connect(link, method) 59 | return json.loads(r.content.decode()) 60 | closure.__doc__ = 'This function will call "%s" on server with method "%s"' % \ 61 | (url, method.upper()) 62 | return closure 63 | 64 | 65 | 66 | 67 | class Client(object): 68 | """Client class to use in your applications""" 69 | def __new__(cls, api_key, service_url): 70 | cls.api_key = api_key 71 | cls.service_url = service_url 72 | pattern = re.compile(r'/api/([\w]+)/.*') 73 | for url in services: 74 | match = re.findall(pattern, url) 75 | if match and services[url]: 76 | for method in services[url]: 77 | new_func_name = '%s_%s' % (translate[method], match[0]) 78 | setattr(cls, new_func_name, connection_factory(cls, url, method)) 79 | return super(Client, cls).__new__(cls) 80 | 81 | def __repr__(self): 82 | output = ['Methods:'] 83 | for i in dir(self): 84 | if i.startswith('get') or i.startswith('add') or i.startswith('remove'): 85 | output.append(' %s: %s'% (i, getattr(getattr(self, i), '__doc__'))) 86 | return '\n'.join(output) 87 | 88 | 89 | if __name__ == '__main__': 90 | c = Client('tt', 'http://192.168.99.100:4000') 91 | #c.services 92 | #print(c.get_user_roles()) 93 | print(c.add_permission(group='myG', name='read_books')) 94 | -------------------------------------------------------------------------------- /auth/CAS/REST/service.py: -------------------------------------------------------------------------------- 1 | 2 | #try: 3 | # import eventlet 4 | # eventlet.monkey_patch() 5 | #except: 6 | # pass 7 | 8 | import falcon 9 | import json 10 | try: 11 | import ujson as json 12 | except ImportError: 13 | pass 14 | 15 | from auth.CAS.authorization import Authorization 16 | 17 | 18 | class AuthComponent(object): 19 | def process_request(self, req, resp): 20 | """Process the request before routing it. 21 | 22 | Args: 23 | req: Request object that will eventually be 24 | routed to an on_* responder method. 25 | resp: Response object that will be routed to 26 | the on_* responder. 27 | """ 28 | 29 | def process_resource(self, req, resp, resource, params): 30 | """Process the request after routing. 31 | 32 | Note: 33 | This method is only called when the request matches 34 | a route to a resource. 35 | 36 | Args: 37 | req: Request object that will be passed to the 38 | routed responder. 39 | resp: Response object that will be passed to the 40 | responder. 41 | resource: Resource object to which the request was 42 | routed. 43 | params: A dict-like object representing any additional 44 | params derived from the route's URI template fields, 45 | that will be passed to the resource's responder 46 | method as keyword arguments. 47 | """ 48 | 49 | def process_response(self, req, resp, resource): 50 | """Post-processing of the response (after routing). 51 | 52 | Args: 53 | req: Request object. 54 | resp: Response object. 55 | resource: Resource object to which the request was 56 | routed. May be None if no route was found 57 | for the request. 58 | """ 59 | if isinstance(resp.body, dict): 60 | try: 61 | resp.body = json.dumps(resp.body) 62 | except(nameError): 63 | resp.status = falcon.HTTP_500 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | def stringify(req, resp): 72 | """ 73 | dumps all valid jsons 74 | This is the latest after hook 75 | """ 76 | if isinstance(resp.body, dict): 77 | try: 78 | resp.body = json.dumps(resp.body) 79 | except(nameError): 80 | resp.status = falcon.HTTP_500 81 | 82 | 83 | class Ping: 84 | def on_get(self, req, resp): 85 | """Handles GET requests""" 86 | resp.body = {'message':'PONG'} 87 | 88 | 89 | class Membership: 90 | def on_get(self, req, resp, client, user, group): 91 | cas = Authorization(client) 92 | resp.body={'result':False} 93 | if cas.has_membership(user, group): 94 | resp.body={'result':True} 95 | 96 | def on_post(self, req, resp, client, user, group): 97 | cas = Authorization(client) 98 | resp.body={'result':False} 99 | if cas.add_membership(user, group): 100 | resp.body={'result':True} 101 | 102 | 103 | def on_delete(self, req, resp, client, user, group): 104 | cas = Authorization(client) 105 | resp.body={'result':False} 106 | if cas.del_membership(user, group): 107 | resp.body={'result':True} 108 | 109 | 110 | class Permission: 111 | def on_get(self, req, resp, client, group, name): 112 | cas = Authorization(client) 113 | resp.body={'result':False} 114 | if cas.has_permission(group, name): 115 | resp.body={'result':True} 116 | 117 | def on_post(self, req, resp, client, group, name): 118 | cas = Authorization(client) 119 | resp.body={'result':False} 120 | if cas.add_permission(group, name): 121 | resp.body={'result':True} 122 | 123 | def on_delete(self, req, resp, client, group, name): 124 | cas = Authorization(client) 125 | resp.body={'result':False} 126 | if cas.del_permission(group, name): 127 | resp.body={'result':True} 128 | 129 | class UserPermission: 130 | def on_get(self, req, resp, client, user, name): 131 | cas = Authorization(client) 132 | resp.body={'result':False} 133 | if cas.user_has_permission(user,name): 134 | resp.body={'result':True} 135 | 136 | class GetUserPermissions: 137 | def on_get(self, req, resp, client, user): 138 | cas = Authorization(client) 139 | resp.body = {'results': cas.get_user_permissions(user)} 140 | 141 | 142 | class GetRolePermissions: 143 | def on_get(self, req, resp, client, role): 144 | cas = Authorization(client) 145 | resp.body = {'results': cas.get_permissions(role)} 146 | 147 | 148 | class GetRoleMembers: 149 | def on_get(self, req, resp, client, role): 150 | cas = Authorization(client) 151 | resp.body = {'result': cas.get_role_members(role)} 152 | 153 | 154 | class GetUserRoles: 155 | def on_get(self, req, resp, client, user): 156 | cas = Authorization(client) 157 | resp.body = {'result': cas.get_user_roles(user)} 158 | 159 | 160 | class ListRoles: 161 | def on_get(self, req, resp, client): 162 | cas = Authorization(client) 163 | resp.body = {'result':cas.roles} 164 | 165 | class WhichRolesCan: 166 | def on_get(self, req, resp, client, name): 167 | cas = Authorization(client) 168 | resp.body = {'result':cas.which_roles_can(name)} 169 | 170 | class WhichUsersCan: 171 | def on_get(self, req, resp, client, name): 172 | cas = Authorization(client) 173 | resp.body = {'result':cas.which_users_can(name)} 174 | 175 | 176 | 177 | 178 | 179 | class Role: 180 | def on_post(self, req, resp, client, role): 181 | cas = Authorization(client) 182 | resp.body={'result':False} 183 | if cas.add_role(role): 184 | resp.body={'result':True} 185 | 186 | 187 | def on_delete(self, req, resp, client, group): 188 | cas = Authorization(client) 189 | resp.body={'result':False} 190 | if cas.del_role(group): 191 | resp.body={'result':True} 192 | 193 | 194 | 195 | api = falcon.API(middleware=[AuthComponent()]) 196 | api.add_route('/ping', Ping()) 197 | api.add_route('/api/membership/{client}/{user}/{group}', Membership()) ## POST DELETE GET 198 | api.add_route('/api/permission/{client}/{group}/{name}', Permission()) ## POST DELETE GET 199 | api.add_route('/api/has_permission/{client}/{user}/{name}', UserPermission()) ## GET 200 | api.add_route('/api/user_permissions/{client}/{user}', GetUserPermissions()) ## GET 201 | api.add_route('/api/role_permissions/{client}/{role}', GetRolePermissions()) ## GET 202 | api.add_route('/api/user_roles/{client}/{user}', GetUserRoles()) ## GET 203 | api.add_route('/api/members/{client}/{role}', GetRoleMembers()) ## GET 204 | api.add_route('/api/role/{client}/{role}', Role()) ## POST DELETE 205 | api.add_route('/api/roles/{client}', ListRoles()) ## GET 206 | api.add_route('/api/which_roles_can/{client}/{name}', WhichRolesCan()) ## GET 207 | api.add_route('/api/which_users_can/{client}/{name}', WhichUsersCan()) ## GET 208 | -------------------------------------------------------------------------------- /auth/CAS/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __author__ = 'Farsheed Ashouri' 4 | 5 | -------------------------------------------------------------------------------- /auth/CAS/authorization.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ## AuthGroup, AuthPermission, AuthMembership 4 | import json 5 | try: 6 | import ujson as json 7 | except ImportError: 8 | pass 9 | 10 | from auth.CAS.models.db import * 11 | from mongoengine.errors import NotUniqueError 12 | 13 | 14 | class Authorization(object): 15 | """ Main Authorization class """ 16 | 17 | def __init__(self, client): 18 | """Initialize Authorization with a client 19 | @type client: String 20 | """ 21 | self.client = client 22 | make_db_connection() 23 | 24 | @property 25 | def roles(self): 26 | """gets user groups""" 27 | result = AuthGroup.objects(creator=self.client).only('role') 28 | return json.loads(result.to_json()) 29 | 30 | def get_permissions(self, role): 31 | """gets permissions of role""" 32 | target_role = AuthGroup.objects(role=role, creator=self.client).first() 33 | if not target_role: 34 | return '[]' 35 | targets = AuthPermission.objects(groups=target_role, creator=self.client).only('name') 36 | return json.loads(targets.to_json()) 37 | 38 | 39 | def get_user_permissions(self, user): 40 | """get permissions of a user""" 41 | memberShipRecords = AuthMembership.objects(creator=self.client, user=user).only('groups') 42 | results = [] 43 | for each in memberShipRecords: 44 | for group in each.groups: 45 | targetPermissionRecords = AuthPermission.objects(creator=self.client, 46 | groups=group).only('name') 47 | 48 | for each_permission in targetPermissionRecords: 49 | results.append({'name':each_permission.name}) 50 | return results 51 | 52 | def get_user_roles(self, user): 53 | """get permissions of a user""" 54 | memberShipRecords = AuthMembership.objects(creator=self.client, user=user).only('groups') 55 | results = [] 56 | for each in memberShipRecords: 57 | for group in each.groups: 58 | results.append({'role':group.role}) 59 | return results 60 | 61 | 62 | def get_role_members(self, role): 63 | """get permissions of a user""" 64 | targetRoleDb = AuthGroup.objects(creator=self.client, role=role) 65 | members = AuthMembership.objects(groups__in=targetRoleDb).only('user') 66 | return json.loads(members.to_json()) 67 | 68 | def which_roles_can(self, name): 69 | """Which role can SendMail? """ 70 | targetPermissionRecords = AuthPermission.objects(creator=self.client, name=name).first() 71 | return [{'role': group.role} for group in targetPermissionRecords.groups] 72 | 73 | def which_users_can(self, name): 74 | """Which role can SendMail? """ 75 | _roles = self.which_roles_can(name) 76 | result = [self.get_role_members(i.get('role')) for i in _roles] 77 | return result 78 | 79 | def get_role(self, role): 80 | """Returns a role object 81 | """ 82 | role = AuthGroup.objects(role=role, creator=self.client).first() 83 | return role 84 | 85 | def add_role(self, role, description=None): 86 | """ Creates a new group """ 87 | new_group = AuthGroup(role=role, creator=self.client) 88 | try: 89 | new_group.save() 90 | return True 91 | except NotUniqueError: 92 | return False 93 | 94 | def del_role(self, role): 95 | """ deletes a group """ 96 | target = AuthGroup.objects(role=role, creator=self.client).first() 97 | if target: 98 | target.delete() 99 | return True 100 | else: 101 | return False 102 | 103 | def add_membership(self, user, role): 104 | """ make user a member of a group """ 105 | targetGroup = AuthGroup.objects(role=role, creator=self.client).first() 106 | if not targetGroup: 107 | return False 108 | 109 | target = AuthMembership.objects(user=user, creator=self.client).first() 110 | if not target: 111 | target = AuthMembership(user=user, creator=self.client) 112 | 113 | if not role in [i.role for i in target.groups]: 114 | target.groups.append(targetGroup) 115 | target.save() 116 | return True 117 | 118 | 119 | def del_membership(self, user, role): 120 | """ dismember user from a group """ 121 | if not self.has_membership(user, role): 122 | return True 123 | targetRecord = AuthMembership.objects(creator=self.client, user=user).first() 124 | if not targetRecord: 125 | return True 126 | for group in targetRecord.groups: 127 | if group.role==role: 128 | targetRecord.groups.remove(group) 129 | targetRecord.save() 130 | return True 131 | 132 | def has_membership(self, user, role): 133 | """ checks if user is member of a group""" 134 | targetRecord = AuthMembership.objects(creator=self.client, user=user).first() 135 | if targetRecord: 136 | return role in [i.role for i in targetRecord.groups] 137 | return False 138 | 139 | 140 | def add_permission(self, role, name): 141 | """ authorize a group for something """ 142 | if self.has_permission(role, name): 143 | return True 144 | targetGroup = AuthGroup.objects(role=role, creator=self.client).first() 145 | if not targetGroup: 146 | return False 147 | # Create or update 148 | permission = AuthPermission.objects(name=name).update( 149 | add_to_set__groups=[targetGroup], creator=self.client, upsert=True 150 | ) 151 | return True 152 | 153 | def del_permission(self, role, name): 154 | """ revoke authorization of a group """ 155 | if not self.has_permission(role, name): 156 | return True 157 | targetGroup = AuthGroup.objects(role=role, creator=self.client).first() 158 | target = AuthPermission.objects(groups=targetGroup, name=name, creator=self.client).first() 159 | if not target: 160 | return True 161 | target.delete() 162 | return True 163 | 164 | def has_permission(self, role, name): 165 | """ verify groups authorization """ 166 | targetGroup = AuthGroup.objects(role=role, creator=self.client).first() 167 | if not targetGroup: 168 | return False 169 | target = AuthPermission.objects(groups=targetGroup, name=name, creator=self.client).first() 170 | if target: 171 | return True 172 | return False 173 | 174 | def user_has_permission(self, user, name): 175 | """ verify user has permission """ 176 | targetRecord = AuthMembership.objects(creator=self.client, user=user).first() 177 | if not targetRecord: 178 | return False 179 | for group in targetRecord.groups: 180 | if self.has_permission(group.role, name): 181 | return True 182 | return False 183 | 184 | -------------------------------------------------------------------------------- /auth/CAS/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ourway/auth/4d99713262dc16671c35497d0388dfbb3f97c9bb/auth/CAS/models/__init__.py -------------------------------------------------------------------------------- /auth/CAS/models/db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Workflow: 5 | 6 | Creating permissions 7 | 8 | First we need some groups, so API user must crete some groups 9 | data = {role:'editors', creator:'my_secret'} 10 | POST /api/authorization/group data 11 | 12 | Then client can assign user: 13 | data = {group:'editors', user='rodmena', creator='my_secret'} 14 | POST /api/authorization/membership 15 | 16 | Now we can add some permissions to editors group: 17 | data = {name:'can_read_my_posts', creator:'my_secret', group:'editors'} 18 | POST /api/authorization/permission data 19 | 20 | 21 | Using permissions: 22 | Request: 23 | GET /api/authorization/has_permission/rodmena?name=can_read_my_posts 24 | Response: 25 | { 26 | "result":true 27 | } 28 | """ 29 | 30 | 31 | __all__ = ['make_db_connection', 'AuthMembership', 'AuthGroup', 'AuthPermission'] 32 | 33 | import os 34 | import datetime 35 | from mongoengine import * 36 | from mongoengine import signals 37 | 38 | 39 | 40 | def make_db_connection(): 41 | mongo_host = os.getenv('MONGO_HOST') or '127.0.0.1' 42 | _mongo_port = os.getenv('MONGO_PORT') or 27017 43 | mongo_port = int(_mongo_port) 44 | connect('Authorization_0x0199', host=mongo_host, port=mongo_port) 45 | 46 | 47 | def handler(event): 48 | """Signal decorator to allow use of callback functions as class decorators.""" 49 | def decorator(fn): 50 | def apply(cls): 51 | event.connect(fn, sender=cls) 52 | return cls 53 | 54 | fn.apply = apply 55 | return fn 56 | return decorator 57 | 58 | 59 | @handler(signals.pre_save) 60 | def update_modified(sender, document): 61 | document.modified = datetime.datetime.now() 62 | 63 | 64 | @update_modified.apply 65 | class AuthGroup(Document): 66 | creator = StringField(max_length=64, required=True) 67 | role = StringField(max_length=32, unique_with='creator', required=True) 68 | description = StringField(max_length=256) 69 | is_active = BooleanField(default=True) 70 | date_created = DateTimeField(default=datetime.datetime.now()) 71 | modified = DateTimeField() 72 | 73 | def __repr__(self): 74 | return '{}: <{}>'.format( 75 | self.__class__.__name__, 76 | self.role 77 | ) 78 | 79 | 80 | @update_modified.apply 81 | class AuthMembership(Document): 82 | user = StringField(max_length=64, unique_with='creator', required=True) 83 | creator = StringField(max_length=64, required=True) 84 | groups = ListField(ReferenceField(AuthGroup)) 85 | is_active = BooleanField(default=True) 86 | date_created = DateTimeField(default=datetime.datetime.now()) 87 | modified = DateTimeField() 88 | 89 | def __repr__(self): 90 | return '{}: <{}>'.format( 91 | self.__class__.__name__, 92 | self.user 93 | ) 94 | 95 | ''' 96 | AuthPermission: 97 | name: can_read_asset_09a8sd08asd09as8d0as 98 | group: reference to group 99 | existance of a record means there is permission. 100 | ''' 101 | 102 | 103 | @update_modified.apply 104 | class AuthPermission(Document): 105 | name = StringField(max_length=64, unique_with='creator', required=True) 106 | creator = StringField(max_length=64, required=True) 107 | groups = ListField(ReferenceField(AuthGroup, required=True)) 108 | is_active = BooleanField(default=True) 109 | date_created = DateTimeField(default=datetime.datetime.now()) 110 | modified = DateTimeField() 111 | 112 | def __repr__(self): 113 | return '{}: <{}>'.format( 114 | self.__class__.__name__, 115 | self.name 116 | ) 117 | 118 | -------------------------------------------------------------------------------- /auth/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Farsheed Ashouri' 2 | from auth.CAS.authorization import Authorization 3 | from auth.CAS.REST.service import api 4 | #from auth.CAS.REST.client import Client 5 | from werkzeug.serving import run_simple as serve 6 | -------------------------------------------------------------------------------- /auth/cmd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ourway/auth/4d99713262dc16671c35497d0388dfbb3f97c9bb/auth/cmd/__init__.py -------------------------------------------------------------------------------- /auth/cmd/server.py: -------------------------------------------------------------------------------- 1 | 2 | from auth import api, serve 3 | import sys 4 | import multiprocessing 5 | CPUs = multiprocessing.cpu_count() 6 | 7 | 8 | _help = ''' 9 | --------------------------------------------------------------- 10 | | GET | /ping | 11 | | GET POST DELETE | /api/permission/{key}/{role}/{action} | 12 | | GET POST DELETE | /api/membership/{key}/{user}/{role} | 13 | | GET | /api/has_permission/{key}/{user}/{action} | 14 | | GET | /api/user_permissions/{key}/{user} | 15 | | GET | /api/user_roles/{key}/{user} | 16 | | GET | /api/role_permissions/{key}/{role} | 17 | | GET | /api/members/{key}/{role} | 18 | | GET | /api/roles/{key}/ | 19 | | GET | /api/which_roles_can/{key}/{action} | 20 | | GET | /api/which_users_can/{key}/{action} | 21 | | POST DELETE | /api/role/{key}/{role} | 22 | --------------------------------------------------------------- 23 | ''' 24 | 25 | 26 | 27 | def main(port=4000): 28 | print('\n\n Python Auth Server ------------\n\t' 29 | 'by: Farsheed Ashouri (@rodmena)\n') 30 | print(_help) 31 | serve('0.0.0.0', port, api, processes=CPUs*2) 32 | 33 | 34 | if __name__ == '__main__': 35 | main() 36 | -------------------------------------------------------------------------------- /docs/API_Usage_Teminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ourway/auth/4d99713262dc16671c35497d0388dfbb3f97c9bb/docs/API_Usage_Teminal.gif -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Auth | Authorization for Humans 3 | ==================================== 4 | 5 | RESTful, Simple Authorization system with ZERO configuration. 6 | 7 | .. image:: https://badge.fury.io/py/auth.svg 8 | :target: https://badge.fury.io/py/auth 9 | 10 | .. image:: https://img.shields.io/pypi/dm/auth.svg 11 | :target: https://pypi.python.org/pypi/auth 12 | 13 | 14 | 15 | 16 | .. image:: https://api.travis-ci.org/ourway/auth.svg 17 | :target: https://travis-ci.org/ourway/auth 18 | 19 | .. image:: https://codecov.io/github/ourway/auth/coverage.svg?branch=master 20 | :target: https://codecov.io/github/ourway/auth?branch=master 21 | 22 | 23 | 24 | 25 | *************** 26 | What is Auth? 27 | *************** 28 | Auth is a module that makes authorization simple and also scalable and powerful. It also has a beautiful RESTful API for use in micro-service architectures and platforms. It is originally desinged to use in Appido, a scalable media market in Iran. 29 | 30 | It supports Python2.6+ and if you have a mongodb backbone, you need ZERO configurations steps. Just type ``auth-server`` and press enter! 31 | 32 | I use Travis and Codecov to keep myself honest. 33 | 34 | ******************* 35 | requirements 36 | ******************* 37 | 38 | You need to access to **mongodb**. If you are using a remote mongodb, provide these environment variables: 39 | 40 | ``MONGO_HOST`` and ``MONGO_PORT`` 41 | 42 | 43 | ******************* 44 | Installation 45 | ******************* 46 | 47 | .. code:: Bash 48 | 49 | pip install auth 50 | 51 | 52 | ******************* 53 | Show me an example 54 | ******************* 55 | ok, lets image you have two users, **Jack** and **Sara**. Sara can cook and Jack can dance. Both can laugh. 56 | 57 | You also need to choose a secret key for your application. Because you may want to use Auth in various tools and each must have a secret key for seperating their scope. 58 | 59 | .. code:: Python 60 | 61 | my_secret_key = "pleaSeDoN0tKillMyC_at" 62 | from auth import Authorization 63 | cas = Authorization(my_secret_key) 64 | 65 | Now, Lets add 3 groups, Cookers, Dancers and Laughers. Remember that groups are Roles. So when we create a group, indeed we create a role: 66 | 67 | .. code:: Python 68 | 69 | cas.add_role('cookers') 70 | cas.add_role('dancers') 71 | cas.add_role('laughers') 72 | 73 | 74 | Ok, great. You have 3 groups and you need to authorize them to do special things. 75 | 76 | .. code:: Python 77 | 78 | cas.add_permission('cookers', 'cook') 79 | cas.add_permission('dancers', 'dance') 80 | cas.add_permission('laughers', 'laugh') 81 | 82 | 83 | Good. You let cookers to cook and dancers to dance etc... 84 | The final part is to set memberships for Sara and Jack: 85 | 86 | .. code:: Python 87 | 88 | cas.add_membership('sara', 'cookers') 89 | cas.add_membership('sara', 'laughers') 90 | cas.add_membership('jack', 'dancers') 91 | cas.add_membership('jack', 'laughers') 92 | 93 | 94 | 95 | That's all we need. Now lets ensure that jack can dance: 96 | 97 | .. code:: Python 98 | 99 | if cas.user_has_permission('jack', 'dance'): 100 | print('YES!!! Jack can dance.') 101 | 102 | 103 | 104 | ********************** 105 | Authorization Methods 106 | ********************** 107 | 108 | use pydoc to see all methods: 109 | 110 | .. code:: Bash 111 | 112 | pydoc auth.Authorization 113 | 114 | 115 | ******************* 116 | RESTful API 117 | ******************* 118 | Lets run the server on port 4000: 119 | 120 | .. code:: Python 121 | 122 | from auth import api, serve 123 | serve('localhost', 4000, api) 124 | 125 | Or, from version 0.1.2+ you can use this command: 126 | 127 | .. code:: Bash 128 | 129 | auth-server 130 | 131 | 132 | Simple! Authorization server is ready to use. 133 | 134 | .. image:: https://raw.githubusercontent.com/ourway/auth/master/docs/API_Usage_Teminal.gif 135 | 136 | 137 | You can use it via simple curl or using mighty Requests module. So in you remote application, you can do something like this: 138 | 139 | .. code:: Python 140 | 141 | import requests 142 | secret_key = "pleaSeDoN0tKillMyC_at" 143 | auth_api = "http://127.0.0.1:4000/api" 144 | 145 | 146 | Lets create admin group: 147 | 148 | .. code:: Python 149 | 150 | requests.post(auth_api+'/role/'+secret_key+'/admin') 151 | 152 | 153 | And lets make Jack an admin: 154 | 155 | .. code:: Python 156 | 157 | requests.post(auth_api+'/permission/'+secret_key+'/jack/admin') 158 | 159 | And finally let's check if Sara still can cook: 160 | 161 | .. code:: Python 162 | 163 | requests.get(auth_api+'/has_permission/'+secret_key+'/sara/cook') 164 | 165 | 166 | 167 | ******************** 168 | RESTful API helpers 169 | ******************** 170 | auth comes with a helper class that makes your life easy. 171 | 172 | .. code:: Python 173 | 174 | from auth.client import Client 175 | service = Client('srv201', 'http://192.168.99.100:4000') 176 | print(service) 177 | service.get_roles() 178 | service.add_role(role='admin') 179 | 180 | 181 | ******************* 182 | API Methods 183 | ******************* 184 | 185 | 186 | .. code:: Bash 187 | 188 | pydoc auth.CAS.REST.service 189 | 190 | 191 | 192 | 193 | - ``/ping`` [GET] 194 | 195 | 196 | Ping API, useful for your monitoring tools 197 | 198 | 199 | - ``/api/membership/{KEY}/{user}/{role}`` [GET/POST/DELETE] 200 | 201 | Adding, removing and getting membership information. 202 | 203 | 204 | - ``/api/permission/{KEY}/{role}/{name}`` [GET/POST/DELETE] 205 | 206 | Adding, removing and getting permissions 207 | 208 | 209 | - ``/api/has_permission/{KEY}/{user}/{name}`` [GET] 210 | 211 | Getting user permission info 212 | 213 | 214 | - ``/api/role/{KEY}/{role}`` [GET/POST/DELETE] 215 | 216 | Adding, removing and getting roles 217 | 218 | 219 | - ``/api/which_roles_can/{KEY}/{name}`` [GET] 220 | 221 | For example: Which roles can send_mail? 222 | 223 | 224 | - ``/api/which_users_can/{KEY}/{name}`` [GET] 225 | 226 | For example: Which users can send_mail? 227 | 228 | 229 | - ``/api/user_permissions/{KEY}/{user}`` [GET] 230 | 231 | Get all permissions that a user has 232 | 233 | - ``/api/role_permissions/{KEY}/{role}`` [GET] 234 | 235 | Get all permissions that a role has 236 | 237 | 238 | - ``/api/user_roles/{KEY}/{user}`` [GET] 239 | 240 | Get roles that user assinged to 241 | 242 | - ``/api/roles/{KEY}`` [GET] 243 | 244 | Get all available roles 245 | 246 | 247 | ******************* 248 | Deployment 249 | ******************* 250 | 251 | Deploying Auth module in production environment is easy: 252 | 253 | 254 | .. code:: Bash 255 | 256 | gunicorn auth:api 257 | 258 | 259 | 260 | 261 | ******************* 262 | Dockerizing 263 | ******************* 264 | 265 | It's simple: 266 | 267 | .. code:: Bash 268 | 269 | docker build -t python/auth-server https://raw.githubusercontent.com/ourway/auth/master/Dockerfile 270 | docker run --name=auth -e MONGO_HOST='192.168.99.100' -p 4000:4000 -d --restart=always --link=mongodb-server python/auth-server 271 | 272 | 273 | 274 | ******************* 275 | Copyright 276 | ******************* 277 | 278 | - Farsheed Ashouri `@ `_ 279 | 280 | 281 | ******************* 282 | Documentation 283 | ******************* 284 | Feel free to dig into source code. If you think you can improve the documentation, please do so and send me a pull request. 285 | 286 | ************************ 287 | Unit Tests and Coverage 288 | ************************ 289 | I am trying to add tests as much as I can, but still there are areas that need improvement. 290 | 291 | 292 | ********** 293 | To DO 294 | ********** 295 | - Add Authentication features 296 | - Improve Code Coverage 297 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mongoengine 2 | falcon 3 | werkzeug 4 | blinker 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | from os import path 4 | from setuptools import setup, find_packages 5 | 6 | MYDIR = path.abspath(os.path.dirname(__file__)) 7 | 8 | # NOTE 9 | REQUIRES = ['falcon', 'mongoengine', 'blinker', 'werkzeug', 'gunicorn', 10 | 'eventlet', 'requests'] 11 | 12 | cmdclass = {} 13 | ext_modules = [] 14 | 15 | setup( 16 | name='auth', 17 | version='0.5.3', 18 | description='Authorization for humans', 19 | long_description=io.open('README.rst', 'r', encoding='utf-8').read(), 20 | classifiers=[ 21 | 'Development Status :: 5 - Production/Stable', 22 | 'Environment :: Web Environment', 23 | 'Natural Language :: English', 24 | 'Intended Audience :: Developers', 25 | 'License :: OSI Approved :: Apache Software License', 26 | 'Operating System :: MacOS :: MacOS X', 27 | 'Operating System :: Microsoft :: Windows', 28 | 'Operating System :: POSIX', 29 | 'Topic :: Software Development :: Libraries :: Python Modules', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: Implementation :: CPython', 32 | 'Programming Language :: Python :: Implementation :: PyPy', 33 | 'Programming Language :: Python :: Implementation :: Jython', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3.5', 36 | ], 37 | keywords='authorizarion role auth groups membership ensure ldap', 38 | author='Farsheed Ashouri', 39 | author_email='rodmena@me.com', 40 | url='http://github.com/ourway/auth/', 41 | license='Apache 2.0', 42 | packages=find_packages(exclude=['tests']), 43 | include_package_data=True, 44 | zip_safe=False, 45 | install_requires=REQUIRES, 46 | setup_requires=[], 47 | cmdclass=cmdclass, 48 | ext_modules=ext_modules, 49 | test_suite='nose.collector', 50 | entry_points={ 51 | 'console_scripts': [ 52 | 'auth-server = auth.cmd.server:main' 53 | ] 54 | } 55 | ) 56 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mongoengine 3 | from mongoengine import connect 4 | from mongoengine.connection import get_connection 5 | 6 | from auth import Authorization 7 | from auth.CAS.models.db import AuthGroup, AuthPermission, AuthMembership 8 | 9 | # CONFTEST 10 | secret_key = 'OnePunchManSaitama' 11 | 12 | @pytest.fixture 13 | def cas(): 14 | return Authorization(secret_key) 15 | 16 | # roles 17 | @pytest.fixture(params=['admin','owner','group','other']) 18 | def role(request, admin='admin', owner='owner', group='group', other='other'): 19 | return locals().get(request.param) 20 | 21 | # permissions 22 | @pytest.fixture 23 | def permissions(): 24 | return ['read','write','execute'] 25 | 26 | # users 27 | @pytest.fixture(params=['sheldon', 'leonard', 'raj', 'howard']) 28 | def member(request, sheldon='sheldon', leonard='leonard', raj='raj', howard='howard'): 29 | return locals().get(request.param) 30 | 31 | # AUTHORIZATION TEST 32 | 33 | def test_authorization_add_role(cas, role): 34 | cas.add_role(role) 35 | assert {'role': role} in cas.roles 36 | 37 | def test_authorization_add_permission_to_role(cas, role, permissions): 38 | if role == 'group': 39 | permissions.remove('write') 40 | if role == 'other': 41 | permissions.remove('write') 42 | permissions.remove('execute') 43 | 44 | for permission in permissions: 45 | cas.add_permission(role, permission) 46 | 47 | for permission in permissions: 48 | assert {'name': permission} in cas.get_permissions(role) 49 | 50 | if role == 'admin' or role == 'owner': 51 | assert cas.has_permission(role, 'read') == True 52 | assert cas.has_permission(role, 'write') == True 53 | assert cas.has_permission(role, 'execute') == True 54 | elif role == 'group': 55 | assert cas.has_permission(role, 'read') == True 56 | assert cas.has_permission(role, 'write') == False 57 | assert cas.has_permission(role, 'execute') == True 58 | else: 59 | assert cas.has_permission(role, 'read') == True 60 | assert cas.has_permission(role, 'write') == False 61 | assert cas.has_permission(role, 'execute') == False 62 | 63 | def test_authorization_role_can(cas, role): 64 | 65 | if role in ('admin', 'owner', 'other'): 66 | assert {'role': role} in cas.which_roles_can('read') 67 | 68 | elif role in ('admin','owner'): 69 | assert {'role': role} in cas.which_roles_can('write') 70 | else : 71 | assert {'role': 'other'} not in cas.which_roles_can('write') 72 | 73 | 74 | def test_authorization_add_member(cas, member): 75 | 76 | association = { 77 | 'sheldon': 'admin', 'leonard': 'owner', 78 | 'raj': 'group', 'howard': 'other' 79 | } 80 | 81 | cas.add_membership(member, association[member]) 82 | 83 | assert cas.has_membership(member, association[member]) 84 | 85 | if cas.has_membership(member, 'admin') or cas.has_membership(member, 'owner'): 86 | assert cas.user_has_permission(member, 'write') == True 87 | assert cas.user_has_permission(member, 'read') == True 88 | assert cas.user_has_permission(member, 'execute') == True 89 | elif cas.has_membership(member, 'group'): 90 | assert cas.user_has_permission(member, 'write') == False 91 | assert cas.user_has_permission(member, 'read') == True 92 | assert cas.user_has_permission(member, 'execute') == True 93 | else: 94 | assert cas.user_has_permission(member, 'write') == False 95 | assert cas.user_has_permission(member, 'read') == True 96 | assert cas.user_has_permission(member, 'execute') == False 97 | 98 | if member == 'howard': 99 | assert 'howard' not in cas.which_users_can('write') 100 | assert {'role': 'other'} in cas.get_user_roles('howard') 101 | 102 | if member == 'raj': 103 | assert {'name': 'read'} in cas.get_user_permissions('raj') 104 | assert {'name': 'write'} not in cas.get_user_permissions('raj') 105 | 106 | 107 | def test_authorization_delete_member(cas): 108 | cas.add_membership('josh', 'other') 109 | assert cas.has_membership('josh', 'other') == True 110 | 111 | cas.del_membership('josh', 'other') 112 | assert cas.has_membership('josh', 'other') == False 113 | 114 | def test_authorization_delete_role(cas): 115 | cas.add_role('intruder') 116 | assert {'role': 'intruder'} in cas.roles 117 | cas.del_role('intruder') 118 | assert {'role': 'intruder'} not in cas.roles 119 | 120 | def test_authorization_delete_permission(cas): 121 | cas.add_permission('admin','fake permission') 122 | assert {'name': 'fake permission'} in cas.get_permissions('admin') 123 | cas.del_permission('admin', 'fake permission') 124 | assert {'name': 'fake permission'} not in cas.get_permissions('admin') 125 | 126 | --------------------------------------------------------------------------------