class Extension(lux.Extension): _config = [ Parameter('WS_URL', '/ws', 'Websocket base url'), Parameter('PUBSUB_STORE', None, 'Connection string for a Publish/Subscribe data-store'), Parameter('WEBSOCKET_HARTBEAT', 25, 'Hartbeat in seconds'), Parameter('WEBSOCKET_AVAILABLE', True, 'Server handle websocket'), ] def on_config(self, app): app.add_events(['on_websocket_open']) def middleware(self, app): '''Add middleware to edit content ''' socketio = SocketIO(app.config['WS_URL']) self.websocket = socketio.handle return [socketio] def on_loaded(self, app): pubsub_store = app.config['PUBSUB_STORE'] if pubsub_store: self.pubsub_store = create_store(pubsub_store) self.websocket.pubsub = self.pubsub_store.pubsub()
class Extension(lux.Extension): _config = [ # Override default email backend Parameter('EMAIL_BACKEND', 'lux.extensions.smtp.EmailBackend', 'Default locale'), Parameter('EMAIL_HOST', '', 'SMTP email host'), Parameter('EMAIL_PORT', 465, 'SMTP email port'), Parameter('EMAIL_HOST_USER', '', 'SMTP email host user'), Parameter('EMAIL_HOST_PASSWORD', '', 'SMTP email host user password'), Parameter('EMAIL_TIMEOUT', None, 'timeout for email timeout'), Parameter('EMAIL_USE_TLS', False, 'use TLS when using SMTP email'), Parameter('EMAIL_TLS_KEYFILE', None, 'TLS Keyfile'), Parameter('EMAIL_TLS_CERTFILE', None, 'TLS cert file'), Parameter('SMTP_LOG_LEVEL', None, 'Logging level for email messages') ] def on_start(self, app, server): level = app.config['SMTP_LOG_LEVEL'] if level: smtp = SMTPLogger(app, level) root = logging.getLogger('') root.addHandler(smtp) for logger in root.manager.loggerDict.values(): if (isinstance(logger, logging.Logger) and not logger.propagate and logger.handlers): logger.addHandler(smtp)
class Extension(lux.Extension): '''Admin site for database data ''' _config = [ Parameter('ADMIN_URL', 'admin', 'Admin site url', True), Parameter('ADMIN_SECTIONS', {}, 'Admin sections information')] def middleware(self, app): admin = app.config['ADMIN_URL'] if admin: app.require('lux.extensions.rest') self.admin = admin = Admin(admin) middleware = [] all = app.module_iterator('admin', is_admin, '__admins__') for AdminRouterCls in all: if not AdminRouterCls._model: continue route = AdminRouterCls() admin.add_child(route) path = route.path() if not path.endswith('/'): middleware.append(RedirectRouter('%s/' % path, path)) middleware.append(admin) return middleware
class Extension(lux.Extension): _config = [ Parameter('OAUTH_PROVIDERS', None, 'Dictionary of dictionary of OAuth providers'), Parameter( 'DEFAULT_OG_TYPE', 'website', 'Default object graph protocol type. ' 'Set to None to disable OGP') ] def on_html_document(self, app, request, doc): doc.meta = NamespaceHeadMeta(doc.head) type = app.config['DEFAULT_OG_TYPE'] if type: # add default OGP entries doc.meta['og:type'] = type doc.meta['og:url'] = app.site_url(request.path) doc.meta['og:locale'] = app.config['LOCALE'] oauths = get_oauths(request) if oauths: for provider in oauths.values(): provider.on_html_document(request, doc) doc.before_render(self.meta_add_tags) doc.jscontext['oauths'] = oauth_context(request) def meta_add_tags(self, request, doc): with OGP(doc) as ogp: if request: oauth = request.cache.oauths for provider in oauth.values(): provider.ogp_add_tags(request, ogp)
class Extension(lux.Extension): '''Object data mapper extension ''' _config = [ Parameter('ADMIN_URL', 'admin', 'Admin site url', True), Parameter('ADMIN_PERMISSIONS', 'admin', 'Admin permission name') ] def middleware(self, app): admin = app.config['ADMIN_URL'] if admin: self.admin = admin = Admin(admin) for model in model_iterator(app.config['EXTENSIONS']): meta = model._meta admin_cls = adminMap.get(meta, AdminModel) if admin_cls: admin.add_child(admin_cls(meta)) permission = app.config['ADMIN_PERMISSIONS'] return [RequirePermission(permission)(admin)] def on_html_document(self, app, request, doc): backend = request.cache.auth_backend permission = app.config['ADMIN_PERMISSIONS'] if backend and not backend.has_permission(request, permission): doc.jscontext.pop('ADMIN_URL', None)
class BrowserBackend(AuthBackend): '''Authentication backend for rendering Forms It can be used by web servers delegating authentication to a backend API or handling authentication on the same site. ''' _config = [ Parameter('LOGIN_URL', '/login', 'Url to login page', True), Parameter('LOGOUT_URL', '/logout', 'Url to logout', True), Parameter('REGISTER_URL', '/signup', 'Url to register with site', True), Parameter('RESET_PASSWORD_URL', '/reset-password', 'If given, add the router to handle password resets', True) ] def on_config(self, app): app.require('lux.extensions.api') def middleware(self, app): middleware = [] cfg = app.config api_url = cfg['API_URL'] if not is_absolute_uri(api_url): api_url = None if cfg['LOGIN_URL']: middleware.append(Login(cfg['LOGIN_URL'], api_url=api_url)) middleware.append(Logout(cfg['LOGOUT_URL'], api_url=api_url)) if cfg['REGISTER_URL']: middleware.append(SignUp(cfg['REGISTER_URL'], api_url=api_url)) if cfg['RESET_PASSWORD_URL']: middleware.append( ForgotPassword(cfg['RESET_PASSWORD_URL'], api_url=api_url)) return middleware
class Extension(lux.Extension): _config = [ Parameter('SERVER_CONFIGURATION', 'nginx_reverse_proxy', ''), Parameter('DOMAIN_NAME', None, 'Full domain name of your web site, e.g. ' 'http://www.example.com') ]
class Extension(lux.Extension): _config = [ Parameter('GZIP_MIN_LENGTH', 200, 'If a positive integer, a response middleware is added so ' 'that it encodes the response via the gzip algorithm.'), Parameter('USE_ETAGS', False, ''), Parameter('CLEAN_URL', True, 'When ``True``, requests on url with consecutive slashes ' 'are converted to valid url and redirected.'), Parameter('SERVE_STATIC_FILES', False, 'if ``True`` add middleware to serve static files.'), Parameter('FAVICON', None, 'Adds wsgi middleware to handle favicon url ``/favicon.ico``' 'served from ``MEDIA_URL/FAVICON``')] def middleware(self, app): '''Add two middleware handlers if configured to do so.''' middleware = [] if app.config['CLEAN_URL']: middleware.append(wsgi.clean_path_middleware) if app.config['SERVE_STATIC_FILES']: icon = app.config['FAVICON'] if icon: middleware.append(FileRouter('/favicon.ico', icon)) path = app.config['MEDIA_URL'] # Check if node modules are available node_modules = os.path.join(os.path.dirname(app.meta.path), 'node_modules') if os.path.isdir(node_modules): node = '%snode_modules/' % path middleware.append(wsgi.MediaRouter(node, node_modules, show_indexes=app.debug)) middleware.append(MediaRouter(path, app.meta.media_dir, show_indexes=app.debug)) return middleware def response_middleware(self, app): gzip = app.config['GZIP_MIN_LENGTH'] middleware = [] if gzip: middleware.append(wsgi.GZipMiddleware(gzip)) if app.config['USE_ETAGS']: middleware.append(self.etag) return middleware def etag(self, environ, response): if response.has_header('ETag'): etag = response['ETag'] elif response.streaming: etag = None else: etag = '"%s"' % hashlib.md5(response.content).hexdigest() if etag is not None: if (200 <= response.status_code < 300 and environ.get('HTTP_IF_NONE_MATCH') == etag): response.not_modified() else: response['ETag'] = etag return response
class Extension(lux.Extension): _config = [ Parameter('HTML5_NAVIGATION', False, 'Enable Html5 navigation', True), Parameter('ANGULAR_VIEW_ANIMATE', False, 'Enable Animation of ui-router views.'), Parameter('NGMODULES', [], 'Angular module to load') ] def on_html_document(self, app, request, doc): router = html_router(request.app_handler) if not router: return # add_ng_modules(doc, app.config['NGMODULES']) # Use HTML5 navigation and angular router if app.config['HTML5_NAVIGATION']: add_ng_modules(doc, LUX_UI_ROUTER) doc.body.data({ 'ng-model': 'page', 'ng-controller': 'Page', 'page': '' }) doc.head.meta.append(Html('base', href="/")) sitemap = angular_sitemap(request, router) add_ng_modules(doc, sitemap.pop('modules')) doc.jscontext.update(sitemap) doc.jscontext['page'] = router.state else: add_ng_modules(doc, LUX_ROUTER) add_ng_modules(doc, router.uimodules) def context(self, request, context): router = html_router(request.app_handler) if request.config['HTML5_NAVIGATION'] and router: pages = request.html_document.jscontext['pages'] page = pages.get(router.state) context['html_main'] = self.uiview(request, context, page) def uiview(self, request, context, page): '''Wrap the ``main`` html with a ``ui-view`` container. Add animation class if specified in :setting:`ANGULAR_VIEW_ANIMATE`. ''' app = request.app main = context.get('html_main', '') if 'templateUrl' in page or 'template' in page: main = Html('div', main, cn='hidden', id="seo-view") div = Html('div', main, cn='angular-view') animate = app.config['ANGULAR_VIEW_ANIMATE'] if animate: add_ng_modules(request.html_document, ('ngAnimate', )) div.addClass(animate) div.data('ui-view', '') return div.render()
class Extension(lux.Extension): _config = [ Parameter('THEME', None, ''), Parameter('ICON_PROVIDER', 'fontawesome', 'Icons provider to use') ] def on_html_document(self, app, request, doc): if doc.has_default_content_type: icon = app.config['ICON_PROVIDER'] if icon == 'fontawesome': doc.head.links.append('fontawesome') doc.data('icon', icon)
class Extension(lux.Extension): _config = [ Parameter('EXCLUDE_EXTENSIONS_CSS', None, 'Optional list of extensions to exclude form the css'), Parameter('NAVBAR_COLLAPSE_WIDTH', 768, 'Width when to collapse the navbar') ] def on_html_document(self, app, request, doc): navbar = doc.jscontext.get('navbar') or {} navbar['collapseWidth'] = app.config['NAVBAR_COLLAPSE_WIDTH'] doc.jscontext['navbar'] = navbar
class Extension(lux.Extension): '''The sessions extensions provides wsgi middleware for managing sessions and users. In addition it provides utilities for managing Cross Site Request Forgery protection and user permissions levels. ''' _config = [ Parameter('CODE_HIGHLIGHT_THEME', 'tomorrow', 'highlight.js theme'), Parameter('CODEMIRROR_THEME', 'monokai', 'codemirror.js theme') ] def on_html_document(self, app, request, doc): add_ng_modules(doc, ('highlight', 'lux.codemirror'))
class BrowserBackend(RegistrationMixin, AuthBackend): '''Authentication backend for rendering Forms in the Browser It can be used by web servers delegating authentication to a backend API or handling authentication on the same site. ''' _config = [ Parameter('LOGIN_URL', '/login', 'Url to login page', True), Parameter('LOGOUT_URL', '/logout', 'Url to logout', True), Parameter('REGISTER_URL', '/signup', 'Url to register with site', True), Parameter('TOS_URL', '/tos', 'Terms of Service url', True), Parameter('PRIVACY_POLICY_URL', '/privacy-policy', 'The url for the privacy policy, required for signups', True) ] LoginRouter = Login LogoutRouter = Logout SignUpRouter = SignUp ForgotPasswordRouter = ForgotPassword def middleware(self, app): middleware = [] cfg = app.config api = cfg['API_URL'] if cfg['LOGIN_URL']: middleware.append( auth_router(api, cfg['LOGIN_URL'], self.LoginRouter, False)) if cfg['LOGOUT_URL'] and self.LogoutRouter: middleware.append( auth_router(api, cfg['LOGOUT_URL'], self.LogoutRouter)) if cfg['REGISTER_URL']: middleware.append( auth_router(api, cfg['REGISTER_URL'], self.SignUpRouter)) if cfg['RESET_PASSWORD_URL']: middleware.append( auth_router(api, cfg['RESET_PASSWORD_URL'], self.ForgotPasswordRouter)) return middleware def on_html_document(self, app, request, doc): if is_absolute_uri(app.config['API_URL']): add_ng_modules(doc, ('lux.restapi', 'lux.users')) else: add_ng_modules(doc, ('lux.webapi', 'lux.users'))
class Extension(lux.Extension): _config = [ Parameter('SITEMAP_CACHE_TIMEOUT', None, ('Sitemap cache timeout, if not set it defaults to ' 'the DEFAULT_CACHE_TIMEOUT parameter')) ]
class Extension(lux.Extension): _config = [ Parameter('ANONYMOUS_GROUP', 'anonymous', 'Name of the group for all anonymous users'), Parameter('DEFAULT_PERMISSION_LEVEL', rest.READ, 'Default permission level') ] def on_config(self, app): app.require('lux.extensions.odm') def on_token(self, app, request, token, user): if user.is_authenticated(): token['username'] = user.username token['user_id'] = user.id token['name'] = user.full_name
class Extension(lux.Extension): _config = [ Parameter('OAUTH_PROVIDERS', None, 'Dictionary of dictionary of OAuth providers'), Parameter( 'DEFAULT_OG_TYPE', 'website', 'Default object graph protocol type. ' 'Set to None to disable OGP'), Parameter( 'CANONICAL_URL', True, 'Add canonical meta tag to website. ' 'Can be a function or False') ] def on_html_document(self, app, request, doc): canonical = app.config['CANONICAL_URL'] if hasattr(canonical, '__call__'): canonical = canonical(request, doc) if canonical: if not isinstance(canonical, str): canonical = request.absolute_uri() doc.head.links.append(canonical, 'canonical') type = app.config['DEFAULT_OG_TYPE'] # add canonical if not available if type: # add default OGP entries doc.meta['og:type'] = type if canonical: doc.meta['og:url'] = canonical doc.meta['og:locale'] = app.config['LOCALE'] doc.meta['og:site_name'] = app.config['APP_NAME'] oauths = get_oauths(request) if oauths: for provider in oauths.values(): provider.on_html_document(request, doc) doc.before_render(self.meta_add_tags) doc.jscontext['oauths'] = oauth_context(request) def meta_add_tags(self, request, doc): '''Add meta tags to the html document just before rendering ''' with OGP(doc) as ogp: if request: oauth = request.cache.oauths for provider in oauth.values(): provider.ogp_add_tags(request, ogp)
class TokenBackend(AuthBackend): '''Backend based on JWT_ Requires pyjwt_ package. .. _pyjwt: https://pypi.python.org/pypi/PyJWT .. _JWT: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html ''' form = LoginForm _config = [ Parameter('AUTHORIZATION_URL', '/authorizations', 'Url for authorizations', True), ] def on_config(self, app): if not jwt: raise ImproperlyConfigured('JWT library not available') def api_sections(self, app): yield Authorization(app.config['AUTHORIZATION_URL']) def request(self, request): '''Check for ``HTTP_AUTHORIZATION`` header and if it is available and the authentication type if ``bearer`` try to perform authentication using JWT_. ''' auth = request.get('HTTP_AUTHORIZATION') user = request.cache.user if auth and user.is_anonymous(): auth_type, key = auth.split(None, 1) auth_type = auth_type.lower() if auth_type == 'bearer': try: data = jwt.decode(key, self.secret_key) except jwt.ExpiredSignature: request.app.logger.info('JWT token has expired') # In this case we want the client to perform # a new authentication. Raise 401 raise Http401('Token') except Exception: request.app.logger.exception('Could not load user') else: user = self.get_user(request, **data) if user: request.cache.user = user def create_token(self, request, user): '''Create the token ''' payload = self.jwt_payload(request, user) return jwt.encode(payload, cfg['SECRET_KEY']) def jwt_payload(self, request, user): expiry = self.session_expiry(request) payload = {'user_id': user.id} if expiry: payload['exp'] = int(time.mktime(expiry.timetuple())) return payload
class Extension(lux.Extension): _config = [ Parameter('NAVIGATION_LEVELS', 1, ''), Parameter('NAVIGATION_BRAND', '', 'Html string for the brand in the toolbar'), Parameter( 'NAVIGATION_BRAND_LINK', '/', 'The href value of the anchor which display the brand in the' ' navigation toolbar'), Parameter('NAVIGATION_TEMPLATE', ('brand', 'main', 'secondary', 'user'), 'Layout of the Navigation Toolbar'), Parameter('NAVIGATION_CLASSES', 'nav nav-list standard', '') ] def on_html_document(self, app, request, doc): '''Add the ``html_navigation`` entry in the ``request.cache``. Loop over all Routers which have the ``navigation_visit`` method implemented. The ``html_navigation`` is an instance of :class:`NavigationInfo` and can be used to add navigation items as well as logos and search boxes. ''' if doc.has_default_content_type: brand = app.config['NAVIGATION_BRAND'] request.cache.html_navigation = NavigationInfo(brand) def navigation(self, request, routers, levels=None): '''Create an ul with links.''' levels = levels or self.config['NAVIGATION_LEVELS'] ul = Html('ul', cn=self.config['NAVIGATION_CLASSES']) for _, router in sorted(self._ordered_nav(routers), key=lambda x: x[0]): if router.parameters.navigation: try: link = router.link() except KeyError: continue ul.append(link) return ul.render(request) def _ordered_nav(self, routers): for router in routers: if router.parameters.navigation: yield router.parameters.navigation, router
class Extension(lux.Extension): _config = [ Parameter('WS_URL', '/ws', 'Websocket base url'), Parameter('WS_HANDLER', LuxWs, 'Websocket handler'), Parameter('WEBSOCKET_HARTBEAT', 25, 'Hartbeat in seconds'), Parameter('WEBSOCKET_AVAILABLE', True, 'Server handle websocket'), ] def on_config(self, app): app.add_events(('on_websocket_open', 'on_websocket_close')) def middleware(self, app): '''Add middleware to edit content ''' handler = app.config['WS_HANDLER'] if handler: socketio = SocketIO(app.config['WS_URL'], handler(app)) return [socketio]
class Extension(lux.Extension): '''Object data mapper extension Uses pulsar-odm for sychronous & asynchronous data mappers ''' _config = [ Parameter('DATASTORE', None, 'Dictionary for mapping models to their back-ends database'), Parameter('DATABASE_SESSION_SIGNALS', True, 'Register event handlers for database session'), Parameter('MIGRATIONS', None, 'Dictionary for mapping alembic settings') ] def on_config(self, app): '''Initialise Object Data Mapper''' app.require('lux.extensions.rest') app.odm = Odm(app, app.config['DATASTORE']) app.add_events(('on_before_commit', 'on_after_commit'))
class Amazon(OAuth2API): WEB_AUTH_URL = 'https://www.amazon.com/ap/oa' BASE_URL = 'https://api.amazon.com' scopes = ('user', 'user:email', 'user:follow', 'public_repo', 'repo', 'repo:status', 'delete_repo', 'notifications', 'gist') auth_class = OAuth2 json = True params = [ Parameter('AMAZON_CLIENT_ID', None, 'Amazon OAUth client ID'), Parameter('AMAZON_CLIENT_SECRET', None, 'Amazon OAUth secret'), Parameter('AMAZON_SCOPE', 'profile', 'Amazon application scope') ] @classmethod def build(cls, cfg=None): if cfg: id = cfg.get('AMAZON_CLIENT_ID') secret = cfg.get('AMAZON_CLIENT_SECRET') if id and secret: return cls(client_id=id, client_secret=secret, client_scope=cfg.get('AMAZON_SCOPE') or 'profile') def html_login_link(self, request): img = Html('img', src=('https://images-na.ssl-images-amazon.com/images' '/G/01/lwa/btnLWA_gold_156x32.png'), width=156, height=32, alt='Login with Amazon') href = self.web_oauth(request) return Html('a', img, href=href) authorization = api_function( '/auth/o2', method='GET', allowed_params={ 'state': None, 'response_type': 'code', # token or code 'redirect_uri': None }, required_params=('response_type', ), allow_redirects=False)
class Extension(lux.Extension): '''Object data mapper extension ''' _config = [ Parameter('DATASTORE', None, 'Dictionary for mapping models to their back-ends database') ] def on_config(self, app): '''Initialise Object Data Mapper''' app.odm = Odm(app, app.config['DATASTORE'])
class Extension(lux.Extension): _config = [ Parameter( 'FONTAWESOME', '//netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome', 'FONTAWESOME url. Set to none to not use it.'), Parameter('BOOTSTRAP', '//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap', 'Twitter bootstrap url. Set to none to not use it.'), Parameter('EXCLUDE_EXTENSIONS_CSS', None, 'Optional list of extensions to exclude form the css'), Parameter('NAVBAR_COLLAPSE_WIDTH', 768, 'Width when to collapse the navbar') ] def on_html_document(self, app, request, doc): navbar = doc.jscontext.get('navbar') or {} navbar['collapseWidth'] = app.config['NAVBAR_COLLAPSE_WIDTH'] doc.jscontext['navbar'] = navbar for name in ('BOOTSTRAP', 'FONTAWESOME'): doc.head.links.append(app.config[name])
class Extension(lux.Extension): _config = [Parameter('TEST_DOCS', False, '')] def middleware(self, app): content = Content('blog', child_url='<int:year>/<month2>/<slug>', html_body_template='blog.html', dir=os.path.join(base, 'content', 'blog'), meta_child={'og:type', 'article'}) # if app.config['TEST_DOCS']: # doc = SphinxDocs('docs', # dir=os.path.join(base, 'content', 'docs')) # content.add_child(doc) return [content]
class Extension(lux.Extension): '''The sessions extensions provides wsgi middleware for managing sessions and users. In addition it provides utilities for managing Cross Site Request Forgery protection and user permissions levels. ''' _config = [ Parameter('CODE_HIGHLIGHT_THEME', 'tomorrow', 'highlight.js theme') ] def on_html_document(self, app, request, doc): ngmodules = set(doc.jscontext.get('ngModules', ())) ngmodules.add('highlight') doc.jscontext['ngModules'] = list(ngmodules)
class Extension(lux.Extension): _config = [Parameter('TEST_DOCS', False, '')] def middleware(self, app): content = HtmlContent('/', Sitemap('/sitemap.xml'), Blog('blog', child_url='<int:year>/<month2>/<slug>', html_body_template='blog.html', dir=base + 'content/blog', meta_child={'og:type', 'article'}), dir=base + 'content/site', meta={'template': 'main.html'}) if app.config['TEST_DOCS']: doc = SphinxDocs('docs', dir=base + 'content/docs') content.add_child(doc) return [content]
class Geonames(API): BASE_URL = 'http://api.geonames.org' #auth_class = auth.KeyAuth json = True params = [Parameter('GEONAMES_USERNAME', None, 'Geonames username')] def setup(self, username=None, **params): self.username = username @classmethod def build(cls, cfg=None): if cfg: un = cfg.get('GEONAMES_USERNAME') if un: return cls(username=un) country_info = api_function('/countryInfoJSON', allowed_params={ 'style': 'full', 'formatted': 'true' }, callback=country_info)
class Extension(lux.Extension): '''Content management System Used by both front-end and back-end. Requires the :mod:`lux.extensions.odm` extension ''' _config = [ Parameter('CMS_LOAD_PLUGINS', True, 'Load plugins from extensions') ] _partials = None def api_sections(self, app): app.require('lux.extensions.odm') return [PageCRUD(), TemplateCRUD()] def on_loaded(self, app): app.cms = CMS(app) def on_html_document(self, app, request, doc): '''Add the ``lux.cms`` module to angular bootstrap ''' add_ng_modules(doc, 'lux.cms')
class AuthMixin(PasswordMixin): '''Mixin to implement authentication backend based on SQLAlchemy models ''' _config = [ Parameter('GENERAL_MAILING_LIST_TOPIC', 'general', "topic for general mailing list") ] def api_sections(self, app): '''At the authorization router to the api ''' yield Authorization() def get_user(self, request, user_id=None, token_id=None, username=None, email=None, auth_key=None, **kw): '''Securely fetch a user by id, username or email Returns user or nothing ''' odm = request.app.odm() if token_id and user_id: with odm.begin() as session: query = session.query(odm.token) query = query.filter_by(user_id=user_id, id=token_id) query.update({'last_access': datetime.utcnow()}, synchronize_session=False) if not query.count(): return if auth_key: with odm.begin() as session: query = session.query(odm.registration) reg = query.get(auth_key) if reg and not reg.confirmed and reg.expiry > datetime.now(): user_id = reg.user_id else: return with odm.begin() as session: query = session.query(odm.user) try: if user_id: user = query.get(user_id) elif username: user = query.filter_by(username=username).one() elif email: user = query.filter_by(email=normalise_email(email)).one() else: return except NoResultFound: return return user def authenticate(self, request, user_id=None, username=None, email=None, user=None, password=None, **kw): odm = request.app.odm() try: if not user: with odm.begin() as session: query = session.query(odm.user) if user_id: user = query.get(user_id) elif username: user = query.filter_by(username=username).one() elif email: email = normalise_email(email) user = query.filter_by(email=email).one() else: raise AuthenticationError('Invalid credentials') if user and self.crypt_verify(user.password, password): return user else: raise NoResultFound except NoResultFound: if username: raise AuthenticationError('Invalid username or password') elif email: raise AuthenticationError('Invalid email or password') else: raise AuthenticationError('Invalid credentials') def has_permission(self, request, name, level): user = request.cache.user # Superuser, always true if user.is_superuser(): return True else: permissions = self.get_permissions(request) return has_permission(request, permissions, name, level) def create_user(self, request, username=None, password=None, email=None, first_name=None, last_name=None, active=False, superuser=False, **kwargs): '''Create a new user. Either ``username`` or ``email`` must be provided. ''' odm = request.app.odm() email = normalise_email(email) assert username or email with odm.begin() as session: if not username: username = email user = odm.user(username=username, password=self.password(password), email=email, first_name=first_name, last_name=last_name, active=active, superuser=superuser) session.add(user) return user def create_superuser(self, request, **params): params['superuser'] = True params['active'] = True return self.create_user(request, **params) def create_token(self, request, user, **kwargs): '''Create the token and return a two element tuple containing the token and the encoded version ''' odm = request.app.odm() ip_address = request.get_client_address() user_id = user.id if user.is_authenticated() else None with odm.begin(close=False) as session: token = odm.token(id=uuid.uuid4(), user_id=user_id, ip_address=ip_address, user_agent=self.user_agent(request, 80), **kwargs) session.add(token) token.encoded = self.encode_token(request, token_id=token.id.hex, user=token.user or user, expiry=token.expiry) session.close() return token def create_auth_key(self, request, user, expiry=None, **kw): '''Create a registration entry and return the registration id ''' if not expiry: expiry = request.cache.auth_backend.auth_key_expiry(request) odm = request.app.odm() with odm.begin() as session: reg = odm.registration(id=digest(user.username), user_id=user.id, expiry=expiry, confirmed=False) session.add(reg) return reg.id def set_password(self, request, user, password): '''Set a new password for user ''' with request.app.odm().begin() as session: user.password = self.password(password) session.add(user) return user def confirm_auth_key(self, request, key): odm = request.app.odm() with odm.begin() as session: reg = session.query(odm.registration).get(key) if reg: session.delete(reg) return True @cached(user=True) def get_permissions(self, request): odm = request.app.odm() user = request.cache.user perms = {} with odm.begin() as session: if user.is_authenticated(): session.add(user) groups = set(user.groups) else: cfg = request.config query = session.query(odm.group) groups = set(query.filter_by(name=cfg['ANONYMOUS_GROUP'])) for group in groups: perms.update(((p.name, p.policy) for p in group.permissions)) return perms
class CsrfBackend(AuthBackend): '''A backend for Cross-Site Request Forgery prevention. Can be used on a session backend. ''' _config = [ Parameter('CSRF_EXPIRY', 60 * 60, 'Cross Site Request Forgery token expiry in seconds.'), Parameter('CSRF_PARAM', 'authenticity_token', 'CSRF parameter name in forms') ] REASON_BAD_TOKEN = "CSRF token missing or incorrect" CSRF_SET = frozenset(('GET', 'HEAD', 'OPTIONS')) def on_form(self, app, form): '''Handle CSRF on form ''' request = form.request param = app.config['CSRF_PARAM'] if (param and form.is_bound and request.method not in self.CSRF_SET): token = form.rawdata.get(param) self.validate_csrf_token(request, token) def on_html_document(self, app, request, doc): if request.method in self.CSRF_SET: cfg = app.config param = cfg['CSRF_PARAM'] if param: csrf_token = self.csrf_token(request) if csrf_token: doc.head.add_meta(name="csrf-param", content=param) doc.head.add_meta(name="csrf-token", content=csrf_token) # CSRF def csrf_token(self, request): if not jwt: # pragma nocover raise ImproperlyConfigured('JWT library not available') session = request.cache.session if session: expiry = request.config['CSRF_EXPIRY'] secret_key = request.config['SECRET_KEY'] return jwt.encode( { 'session': session.get_key(), 'exp': time.time() + expiry }, secret_key) def validate_csrf_token(self, request, token): if not jwt: # pragma nocover raise ImproperlyConfigured('JWT library not available') if not token: raise PermissionDenied(self.REASON_BAD_TOKEN) try: secret_key = request.config['SECRET_KEY'] token = jwt.decode(token, secret_key) except jwt.ExpiredSignature: raise PermissionDenied('Expired token') except Exception: raise PermissionDenied(self.REASON_BAD_TOKEN) else: if token['session'] != request.cache.session.get_key(): raise PermissionDenied(self.REASON_BAD_TOKEN)