Authentication and Authorization module for Bottle Web Apps
Project description
BottleAuth - Authentication and Authorization Middleware
The BotAuth() is a plugin class for the Bottle web framework.:
- BotAuth compliments a set of Bottle authentication modules by providing simplified authentication and authorization mechanisms for building web apps. Supported authentication modules include:
- BotAuth establishes route-based and prefix-based authorization requirements, using attribute/value pairs. These requirements evaluate against the attributes obtained from the authenticaiton module for the user.
- BotAuth utilizes BottleSessions to provide persistent session access to the authenticator data.
- BotAuth Complies with Bottle Plugin API v2.
Quickstart
pypi installation:
# python3 -m pip install BotAuth
Additionally, the desired authentication module will need to be installed.
Initializing BotAuth Middlware
Adding BotAuth to a bottle app:
from bottle import Bottle
from BottleSessions import BottleSessions
from BottleSaml import SamlSP
from BotAuth import BotAuth
# import SAML configuration
from config import saml_config
# Create a Bottle application context
app = Bottle()
# Install session middleware (Before SAML)
BottleSessions(app)
# Create the SAML Service Provider (SP)
# See READMESP.md for details
saml = SamlSP(app, saml_config=saml_config)
# Configure and install BotAuth middleware
auth = BotAuth(saml)
app.install(auth)
The default auth=BotAuth(saml)
configuration provides Opt-Out authentication on all routes but establishes no authorization requirements.
Requiring Authentication
BotAuth provided two modes for requiring Authentication: Bottle apps can Opt-Out, or Opt-In to authentication.
Opt-Out Authentication
By default BotAuth adds an authentication requirement on all views in the app.
An unauthenticated user accessing any view will invoke the authentication process specific to the specific module (e.g. SAML, CAS, or OIDC), requiring successful login to access the page.
This isn't always the desired behavior. There are usually some views in your application that you would like accessible even to unauthenticated users.
You can Opt-Out of authentication on these views using the skip=
argument to the @app.route(...)
, listing the BotAuth plugin instance (skip=auth
) or by plugin name (skip='BotAuth'
)
Opting-Out of authentication on a view:
saml = SamlSP(app, saml_config)
auth = BotAuth(saml, authn_all_routes=True)
app.install(auth)
---
@app.route('/help', skip=[auth])
def help_view():
return `This is an unprotected route`
The BotAuth() parameter authn_all_routes
set to True
(the default) establishes the requirement to Opt-Out.
Opt-In Authentication
BotAuth also has an Opt-In
mode.
Some web apps require authentication on only a few views. For these it is easier to Opt-in
those page by adding authn=True
(or authz=
) in the app.route(...)
config options.
Opt-In authentication:
saml = SamlSP(saml_config)
auth = BotAuth(saml, authn_all_routes=False)
app.install(auth)
---
# SET Opt-In mode
auth.authn_all_routes = False
---
@app.route('/login', authn=True)
def login_view():
return 'You are now logged in.'
Opt-In behavior is established by BotAuth instantiated with authn_all_routes
set to False
.
To summerize, Opt-In/Opt-Out behavior is set for the whole app by the BotAuth() parameter authn_all_routes
,
and Opt-Out is the default behavior.
Requiring Authorization
BotAuth provides two methods of applying authorization requirements to views in a Bottle web app. These are route-based authorization, and path-based authorization.
Route-based Authorization
Route authorization restrictions are added to a views with the authz={...}
config option in app.route(...)
decorator.
Route based authorization can be used in either Opt-in or Opt-out mode, and any authz
statement provides implicit authn=True
authentication.
Authorization restrictions of authz={attr:value}
are defined by attribute/value pairs in Python dict
form.
route-based authorization:
@app.route('/alices_page', authz={'username': 'alice})
def alice_only():
return 'hi Alice'
The values and attributes are compared to attributes acquired from the authentication module for the authenticated user and stored in the users session.
The authz
requirements are met if the user has the same attribute, and the value for that attribute matches the authz statement. If so, the request reaches the protected view.
Failure to match these required value or not posessing the attribute results in a 403 Unauthorized error.
Multiple value (list) matching:
If multiple values for an attribute are specified using a list (e.g. authz={'username':['bob','alice]}
), the user needs to match only one of the list and the restriction met.
Multiple atttribute matching:
If multiple attributes are specified, (e.g. authz={'group': ['sysadmin','netadmin'], 'mfa': True}
) the user must match at least one value for each attribute. The user must have all the attributes, and all the attributes must be satisfied for the restriction to be met.
Route-based Authorization Examples
Consider these examples:
Multiple
authz
examples:
# Only username bob can access view
@app.route('/bob', authz={'username':'bob'})
def bob_only_view()
return 'Hi Bob'
# Anyone in the sysadmin group
@app.route('/sysadmins', authz={'groups':'sysadmins'}
)
def sysadmin_view():
return 'Hi sysadmin!'
# Anyone in sysadmin, netadmin, or cloudadmins
@app.route('/alladmins', authz={'groups': 'sysadmins', 'netadmins', 'cloudadmins']})
def alladmin_view():
return 'Hi various admins!'
# User alice or bob, but only with MFA authentication
@app.route('/multiple', authz={
'username' : ['alice', 'bob'],
'mfa' : True})
def mfaonly():
return 'You\'ve used your MFA!'
Tip: To keep authorizations managable, construct a dict
for each use-case and apply it **kwargs
style in @app.route()
. Reuse them on routes with the same authorization requirement:
Example of use case authorization:
admin_restricted = {'groups': ['sysadmins', 'netadmins', 'cloudadmins']}
budgets = {'groups': ['project_team','accounting']}
skip_auth = {'skip':[saml]}
@app.rotue('/greeting', **skip_auth)
def greet():
return 'Hello'
@app.route('/admin', **admin_restricted)
def admin_only():
return 'Hello admin'
@app.route('/budget/2022', **budgets)
def budget_2022():
return 'Hello EXCELers'
@app.route('/route53', **admin_restricted)
def r53():
return 'Hello again admins'
This is not required, but makes it a little bit easier to write and read.
Prefix-based Authorization
Often sites have structured layouts based on paths and formed in a logical tree, say, based on project, department, or customer. Prefix-based authorization provides an easy way to provide authorization restrictions.
Example logical tree of Departments:
/ --+- helpdesk
|
+- infrastructure
| |
| +-- Networking
| |
| +-- Servers
| |
| +-- Storage
+- desktop
|
+-- PC_support
|
+-- MAC_support
There are implicit authorization rules that can be created based on the prefix path of views routes:
Example prefix-based authorization
# config.py segment
it_prefix_auth={
# The /helpdesk path can be access by department
'/helpdesk' : {
'dept':['helpdesk','PC support']
},
# /Infrastructure accessed by 3 groups
'/infrastructure': {
'groups':['sysadmin','netadmin', 'storageadmin']
},
# The further down the tree, the fewer groups
'/infrastructure/Networking': {
'groups':'networking'
},
'/infrastructure/Servers': {
'groups':'sysadmin'
},
# Other attributes than groups can be used
'/infrastructure/Storage': {
'role': ['emcadmin','pureadmin']
},
'/desktop/PC_support':{
'dept': ['PC support']
},
'/desktop/MAC_support': {
'username': ['bob', 'alice']
}
}
These requirements are attached to views based on their route path.
Applying prefix-based authorization:
from config import saml_config, it_prefix_auth
saml = SamlSP(saml_config=saml_config)
auth = BotAuth(saml, authz_by_prefix=it_prefix_auth)
...
@app.route('/desktop/MAC_support')
def hello_mac():
return 'Hello Alice or Bob'
Again, best practice may be to have this in a config.py file.
Excluding views from path-based authorization:
Routes can be excluded from path-based authorization requirements using the skip=[auth]
method.
Using route-based with path-based authorization:
Additional requirements can be placed individual views using the authz={...}
route config. Both the path-based requirements and the authz requirements must be met for access to the view to be granted.
Opt-out is required for path-based authorization:
Path-based routes only function in Opt-Out mode (i.e. auth_all_routes=True) This better matches intent of path-based restrictions.
Available Attributes for Authorization
All examples here are based on the SamlSP() authenticator, but are identical with BottleOIDC BotOIDC() and BottleCasClient CasSP(). The attributes you have available to BotAuth depends on the authentication module used, and the configuration of the associated IdP. Consult the documentation for the specific authentication module you are using.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for BottleAuth-21.9.7-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f9613d982bd014e14eb90faee77a1bcd009f67f8d7b786ff91e65a14f6fff92d |
|
MD5 | 6e233b1ee7f0549aa3800620cfef1a1f |
|
BLAKE2b-256 | 59f8ae9dcaebf17c260e50889bc4c526cf441494ff3e3bdd7b0f2d1bf09941bd |