def _validate_request_code(self, code, client_id, callback): ''' _validate_request_code - internal method for verifying the the given nonce. also removes the nonce from the data_store, as they are intended for one-time use. ''' nonce = yield Task(self.data_store.fetch, 'nonce_codes', code=code) if not nonce: raise Proauth2Error('access_denied', 'invalid request code: %s' % code) if client_id != nonce['client_id']: raise Proauth2Error('access_denied', 'invalid request code: %s' % code) user_id = nonce['user_id'] expires = nonce['expires'] yield Task(self.data_store.remove, 'nonce_codes', code=code, client_id=client_id, user_id=user_id) if time() > expires: raise Proauth2Error('access_denied', 'request code %s expired' % code) callback(user_id)
def request_authorization(self, client_id, user_id, response_type, redirect_uri=None, scope=None, state=None, expires=600, callback=None): ''' request_authorization generates a nonce, and stores it in the data_store along with the client_id, user_id, and expiration timestamp. It then returns a dictionary containing the nonce as "code," and the passed state. --- response_type MUST be "code." this is directly from the OAuth2 spec. this probably doesn't need to be checked here, but if it's in the spec I guess it should be verified somewhere. scope has not been implemented here. it will be stored, but there is no scope-checking built in here at this time. if a redirect_uri is passed, it must match the registered redirect_uri. again, this is per spec. ''' if response_type != 'code': raise Proauth2Error('invalid_request', 'response_type must be "code"', state=state) client = yield Task(self.data_store.fetch, 'applications', client_id=client_id) if not client: raise Proauth2Error('access_denied') if redirect_uri and client['redirect_uri'] != redirect_uri: raise Proauth2Error('invalid_request', "redirect_uris don't match") nonce_code = self._generate_token() expires = time() + expires try: yield Task(self.data_store.store, 'nonce_codes', code=nonce_code, client_id=client_id, expires=expires, user_id=user_id, scope=scope) except Proauth2Error as e: e.state = state raise e callback({'code': nonce_code, 'state': state})
def store(self, collection, **kwargs): ''' validate the passed values in kwargs based on the collection, store them in the mongodb collection ''' key = validate(collection, **kwargs) if self.fetch(collection, **{key: kwargs[key]}): raise Proauth2Error('duplicate_key') self.db[collection].insert(kwargs)
def _auth(self, client_id, key, method, callback): ''' _auth - internal method to ensure the client_id and client_secret passed with the nonce match ''' available = auth_methods.keys() if method not in available: raise Proauth2Error('invalid_request', 'unsupported authentication method: %s' 'available methods: %s' % \ (method, '\n'.join(available))) client = yield Task(self.data_store.fetch, 'applications', client_id=client_id) if not client: raise Proauth2Error('access_denied') if not auth_methods[method](key, client['client_secret']): raise Proauth2Error('access_denied') callback()
def authenticate_token(self, token, callback): ''' authenticate_token checks the passed token and returns the user_id it is associated with. it is assumed that this method won't be directly exposed to the oauth client, but some kind of framework or wrapper. this allows the framework to have the user_id without doing additional DB calls. ''' token_data = yield Task(self.data_store.fetch, 'tokens', token=token) if not token_data: raise Proauth2Error('access_denied', 'token does not exist or has been revoked') callback(token_data['user_id'])
def store(self, collection, **kwargs): ''' validate the passed values in kwargs based on the collection, store them in the mongodb collection ''' callback = kwargs.pop('callback') key = validate(collection, **kwargs) data = yield Task(self.fetch, collection, **{key: kwargs[key]}) if data is not None: raise Proauth2Error('duplicate_key') yield Op(self.db[collection].insert, kwargs) callback()
def validate( table, **data ): ''' theoretically, any data store can be implemented to work with this package, which means basic data validation must be done in-package, so that weird stuff can't be stored in the data store. this function raises an exception if an invalid table name is passed, not all of the required fields are in the data kwargs, or if a field that was passed is not expected. it also returns the key field name, for ensuring uniqueness (again, that may not be built into whatever data store is impelemented.) ''' if table not in good.keys(): raise Proauth2Error( 'invalid_request', 'invalid name: %s' % table ) for req in good[table]['required']: if not data.get( req, None ): raise Proauth2Error( 'invalid_request', 'missing required field: %s' % req ) for key in data.keys(): if key not in good[table]['required'] and \ key not in good[table]['optional']: raise Proauth2Error( 'invalid_request', 'invalid field: %s' % key ) return good[table]['key']
def request_access_token(self, client_id, key, code, grant_type, redirect_uri=None, method='direct_auth', callback=None): ''' request_access_token validates the client_id and client_secret, using the provided method, then generates an access_token, stores it with the user_id from the nonce, and returns a dictionary containing an access_token and bearer token. --- from the spec, it looks like there are different types of tokens, but i don't understand the disctintions, so someone else can fix this if need be. regarding the method: it appears that it is intended for there to be multiple ways to verify the client_id. my assumption is that you use the secret as the salt and pass the hashed of the client_id or something, and then compare hashes on the server end. currently the only implemented method is direct comparison of the client_ids and client_secrets. additional methods can be added to proauth2.auth_methods ''' if grant_type != 'authorization_code': raise Proauth2Error('invalid_request', 'grant_type must be "authorization_code"') yield Task(self._auth, client_id, key, method) user_id = yield Task(self._validate_request_code, code, client_id) access_token = self._generate_token(64) yield Task(self.data_store.store, 'tokens', token=access_token, user_id=user_id, client_id=client_id) callback({'access_token': access_token, 'token_type': 'bearer'})