Package: pyramid-oauthlib-lowlevel
pyramid_oauthlib_lowlevelThis is a package designed to create oAuth servers with Pyramid through a lowlevel interface that allows for more customization.Before using this package I suggest you consider the packagepyramid-oauthlib, it is possibly better suited to your needs as it offers a highlevel interface.Seriously. This package provides a very lowlevel interface to customizing theoauthlibpackage within Pyramid.  Most people will want to use the higher level abstractions with a more simplified package.What do I mean by highlevel vs lowlevel?highlevel:pyramid-oauthlibmakes a lot of hard decisions for you, and gives you a handful of hooks to easily implement oAuth flows into your Pyramid application.  You don't need to know much about oAuth to utilize it.lowlevel:pyramid-oauthlib-lowlevelprovides a very basic integration scheme for leveraging the oauthlib library in Pyramid applications.  You generally need to know quote a bit about oAuth to handle this.Some use-cases for this package are situations where:You need to support oAuth1. (pyramid-oauthlibonly supports oAuth2)You need more fine-grained control over the oAuth2 integration or to host multiple oAuth2 servers.pyramid-oauthlibexpects you to be doing things a certain way (which is very common and the status-quo).You need to support/create broken oAuth servers.*Broken oAuth servers? Yes. Even major internet services often implement oAuth in ways that do not fully adhere to the specs, or violate them. Offline integrated test suites may need to create servers which can mimic this behavior.The package generally works by defining one or more subclasses to handle getting/setting data objects, then invoking them on Pyramid requests via explicitly designed oAuth providers.This approach will allow you to create multiple oAuth servers and/or authentication methods running on the same Pyramid serverThe rational for developing with this approach was a need to:support multiple oAuth versions and endpoints on a whitelabel servicelimit and isolate the oAuth data from the Pyramid request.This project was largely modeled afterflask-oauthliband the coreoauthlibpackage. Docstrings in this package are taken from both.  Significant portions of code in this package are a port offlask-oauthlibtoPyramid; many portions adapt bits straight fromoauthlibas well.How This Package is Generally DesignedBothoauth1andoauth2namespaces offer aproviderandvalidatornamespace.Theoauth1.validator.OAuth1RequestValidator_Hooksandoauth2.validator.OAuth2RequestValidator_Hooksclasses provide hooks for theRequestValidatorto get/set data from your database and cache. In every deployment these must be subclassed.Theoauth1.validator.OAuth1RequestValidatorandoauth2.validator.OAuth2RequestValidatorclasses provide the logic used to handle oAuth requests, adapted to Pyramid. Only in rare cases should these be subclassed.Theoauth1.provider.OAuth1Providerandoauth2.provider.OAuth2Providerprovide methods which invoke theRequestValidatorunder Pyramid endpoints as logical units of work that are either endpoints or resource validators. Only in rare cases should these be subclassed.oauth1.provider.OAuth1Provider.endpoint__request_token.endpoint__access_token.extract__endpoint_authorize_data.endpoint__authorize__authorize.logic__is_authorizedoauth2.provider.OAuth2Provider.endpoint__validate_authorization_request.endpoint__confirm_authorization_request.endopoint__token.endpoint__revoke_token.verify_requestTypically usage will involve these steps:subclass ahooksobject in the applicationon a request, first create a new instance of aproviderwith the customized hookon a request, then invoke the desired endpoint or request validation.The Tests are Fully Functional Examples!The tests contain full example applications/servers and logic flows that iterate through different oAuth strategies.  For example, the oAuth2 test include (oAuth1 tests are identical):tests.oauth2_appdefines a fully functional oAuth2 server which can be spun uptests.oauth2.PyramidTestAppspins up atests.oauth2_appinstance, and uses that to make requeststests.oauth2.PyramidTestApp.test_valid_flow__get_access_tokentests a flow for obtaining a "bearer_token" from the oAuth2 servertests.oauth2_modeldefines the persistence models (SqlAlchemy) used by unit tests and the test appstests.oauth2_utilsdefines the subclasses and utilities used for both the unit tests and test appsSupported FlowsAll oAuth flows will ideally be supported. PRs are very much welcome!Currently the supported flows are:oAuth1 - Application Authorization - New Account Registration (seetests.oauth1.PyramidTestApp.test_valid_flow__registration)oAuth2 - Application Authorization - New Account Registration (seetests.oauth2.PyramidTestApp.test_valid_flow__registration)oAuth2 - Obtain Bearer Token (seetests.oauth2.PyramidTestApp.test_valid_flow__get_access_token)oAuth2 - Refresh Token (seetests.oauth2.PyramidTestApp.test_valid_flow__registration)oAuth2 - Revoke Token (seetests.oauth2.PyramidTestApp.test_valid_flow__get_access_token)Todo:oAuth1 - Application Authorization - Bind Existing AccountoAuth2 - Application Authorization - Bind Existing AccountNotes:New Account Registrationmeans a new account is established onApplicationfor an existing user ofAuthorityBind Existing Accountmeans an existing user ofApplicationwith connect their account to their existing account onAuthorityThese above elements both use the same oAuth flows and endpoints, they just integrate them slightly differentlyTutorialHere is a quick way to design an oAuth server:Subclass OAuth1RequestValidatorthis is optionalclass CustomValidator(OAuth1RequestValidator):
"""some validator methods do need overrides."""@property
 def realms(self):
 	return ['platform.actor', ]

 @property
 def client_key_length(self):
 	return (40, 64)

 ...Subclass OAuth1RequestValidator_Hooksthe library provides a@catch_backend_failureto wrap backend failures with more meaningclass CustomValidator_Hooks(OAuth1RequestValidator_Hooks):
"""
This custom object expects a SqlAlchemy connection onself.pyramid_request.dbSession"""@catch_backend_failure
 def _get_TokenRequest_by_verifier(self, verifier, request=None):
 	"""
 	:param verifier: The verifier string.
 	:param request: An oauthlib.common.Request object.
 	"""
 	verifierObject = sqla.get_Developer_oAuth1Server_TokenRequest__by_oauthVerifier(
 		self.pyramid_request.dbSession,
 		verifier,
 	)
 	return verifierObjectCreate an oAuth objectthis might be a request property via @reifydef new_oauth1Provider(request):
"""this is used to build a new auth"""
validatorHooks = CustomValidator_Hooks(request)
provider = pyramid_oauthlib_lowlevel.oauth1.provider.OAuth1Provider(request,
validator_api_hooks = validatorHooks,
validator_class = CustomValidator
)
return providerUse the oAuth object - Checking for API accessWithin your authorization routines, you can check for a valid oauth requestoauth1Provider = new_oauth1Provider(request)
_is_authorized, req = oauth1Provider.logic__is_authorized(['platform.actor', ])
if not _is_authorized:
raise HTTPUnauthorized(body="""'{"error": "Not Authorized (oAuth Failed)}'""", content_type='application/json')perhaps do something withreq.clientandreq.access_tokenUse the oAuth object - routes for granting accessSee the testappsIntegrationBecause this is a_lowlevellibrary, there is no automagic integration of this package into Pyramid.In the example apps, convenience methods are used to create an oauth provider as needed:def new_oauth2Provider(pyramid_request):
	validatorHooks = CustomValidator_Hooks(pyramid_request)
	provider = oauth2_provider.OAuth2Provider(pyramid_request,
											  validator_api_hooks = validatorHooks,
											  validator_class = CustomValidator
											  )
	return providerA deployment might handle this as part of Pyramid AUTH, within Tweens or Middleware, as a request property, or many other options.The oauth2 testapp has a particular flow worth noting:ExampleApp.fetch_protected_resourceA user must log into ExampleApp and have an authorized token for the Authority system.  This route will load the token and use it to make an oAuth2 request against the Authority system.Authority_Oauth2_API_Public.protected_resourceThe resource is protected behind an oauth2 token validation.oauth2Provider = new_oauth2Provider(self.request)
    scopes = ['platform.actor', ]
    valid, req = oauth2Provider.verify_request(scopes)oAuth FlowsThere are many different flows one can do in oAuthThis project is currently aimed at implementing the following families of flows:Flow-AccountRegistration - a user ofAuthorityestablishes a new account onApplicationFlow-AccountBind - an existing user ofApplicationlinks their account toAuthorityFlow-Developer - a user ofAuthorityhas created anApplicationand needs an access tokenNotes on Token Expiration and RefreshThere are several strategies and concerns for expiring access and refresh tokens during a refresh.Some deployments wish to expire all access tokens for a user when a new access_token is generated.This is illustrated inoauth2_utils.OAuth2RequestValidator_Hooks.bearer_token_setter, which sets existing live tokens for a user/client as inactive.Some deployments will not want to do this, such as cloud based systems which may take some time to propagate new credentials.Some deployments will wish to recycle arefresh_tokenacross multiple access_token refreshes.  This is supported by overriding a default method inOAuth2RequestValidatorclass CustomValidator(OAuth2RequestValidator):

	def rotate_refresh_token(self, request):
		"""Determine whether to rotate the refresh token. Default, yes.

		When access tokens are refreshed the old refresh token can be kept
		or replaced with a new one (rotated). Return True to rotate and
		and False for keeping original.

		:param request: oauthlib.common.Request
		:rtype: True or False

		Method is used by:
			- Refresh Token Grant
		"""
		return TrueBoth forms are covered in the test suite (the default is overridden by monkeypatching the Request Validator, then it is reset)Some deployments will want to DELETE a token when it expires or is revoked.  This library is designed to support using database flags to mark if a token is active, revoked, expired, etc.  This is designed for concerns in bookkeeping.Notes on Refreshing TokensOAuthlib is an excellent library, but there are some minor inconveniences you may need to look out for when refreshing a token.OAuthlib does not cache the refresh_token's object, it merely validates it or it's parameters.  Unless you specifically cache it, you may load it multiple times in a single request for each validation operation.  Many packages that utilize OAuthlib do not account for this implementation detail - this package included.OAuthlib does not maintain the lineage of a token across refreshes. This is important if your application supports more than one type of grant (such as supporting bothauthorization_codeANDclient_credentials).  To get around this, we recommend doing the following:extend the storage for tokens with the following fields:grant_typeoriginal_grant_typeextendbearer_token_setterto do the following:store therequest.grant_typeonto the new token object when you save it to the database.if thegrant_typeis not "refresh_token", store that value as theoriginal_grant_typeif thegrant_typeis "refresh_token", then load the "refresh_token" object (request.refresh_token), and copy theoriginal_grant_typeThis strategy will allow you to easily differentiate between client_credential (Application) and authorization_code (User) tokens.Python Compatibilitypyramid_oauthlib_lowlevelis tested to run under Python2.7 and Python3.6+LicenseSome functions are ported from oauthlib and flask-oauthlib;
see LICENSE.txtSome functions are ported from Pyramid and appear under their License;
see LICENSE-Pyramid.txt