Package: ska
Lets you easily sign data, using symmetric-key algorithm encryption. Allows
you to validate signed data and identify possible validation errors. Uses
sha-(1, 224, 256, 385 and 512)/hmac for signature encryption. Allows to use
custom hash algorithms. Comes with shortcut functions for signing (and
validating) dictionaries and URLs.Key conceptsHosts, that communicate with each other, share the Secret Key, which is used
to sign data (requests). Secret key is never sent around.One of the cases is signing of HTTP requests. Each (HTTP) request is signed
on the sender side using the shared Secret Key and as an outcome produces the
triple (signature,auth_user,valid_until) which are used to sign
the requests.signature(str): Signature generated.auth_user(str): User making the request. Can be anything.valid_until(floatorstr): Signature expiration time (Unix timestamp).On the recipient side, (HTTP request) data is validated using the shared
Secret Key. It’s being checked whether signature is valid and not expired.┌─────────────┐           Data              ┌─────────────┐
│   Host 1    ├────────────────────────────>│   Host 2    │
│ ─────────── │                             │ ─────────── │
│ secret key  │                             │ secret key  │
│ 'my-secret' │<────────────────────────────┤ 'my-secret' │
└─────────────┘           Data              └─────────────┘FeaturesCoreskamoduleSign dictionaries.Validate signed dictionaries.Sign URLs. Append and sign additional URL data.Validate URLs.Use one of the built-in algorythms (HMAC SHA-1, HMAC SHA-224, HMAC SHA-256,
HMAC SHA-384 or HMAC SHA-512) or define a custom one.Djangoskamodule (ska.contrib.django.ska)Model decorators for signing absolute URLs. View (including class-based
views) decorators for protecting views to authorised parties only (no
authentication required).Authentication backend for Django based on the signatures (tokens) generated
usingska, which allows you to get a password-less login to Django web
site. Multiple Secret Keys (per provider) supported. Comes with handy
callbacks (possible to customise per provider) for various states of
authentication.Template tags for signing URLs from within templates.django-constanceintegration (for password-less authentication).Django REST Framework integration(for protecting ViewSets, obtaining
JWT tokens for authentication).PrerequisitesPresentCoreskamodule requires Python 3.8, 3.9, 3.10 and 3.11.Djangoskamodule (ska.contrib.django.ska) requires the mentioned
above plus Django 3.2, 4.1 or 4.2. Additionally, certain
versions ofdjango-constanceanddjangorestframeworkare required.
Specific version requirement primarily depends on the used Django version.
Check theexample requirementsto find out which versions ofdjango-constanceanddjangorestframeworkhave been tested with specific Django versions.PastNoteIn future releases (any time) compatibility with no-longer-supported
versions might/will be wiped out.Dropping support of Python 3.6 and 3.7 has been announced in version 1.10.
As of 1.9.1 everything still worked.Dropping support of Python 2.7 and 3.5 has been announced in version 1.8.
As of 1.7.5 everything still worked.Dropping support of Python 3.4 has been announced in version 1.6.8. As of
1.6.8 everything still worked.Dropping support of Django 2.2, 3.0, 3.1 and 4.0 has been announced in
version 1.10. As of 1.9.1 everything is still backwards compatible with
mentioned versions.Dropping support of Django 1.5, 1.6 and 1.7 has been announced in version
1.6. As of 1.6 everything is still backwards compatible with mentioned
versions.Dropping support of Python 2.6 and 3.3 has been announced in version 1.6.
As of 1.6 everything is still backwards compatible (as much as it’s possible
within this package) with mentioned versions.Eco-systemNeedskafor other languages? Check the following affiliated projects:skajs:skaimplementation
for NodeJS (both CommonJS and ESM are supported, Node >= 14).skaphp:skaimplementation
for PHP (>= 7.2).Generated signatures are inter-compatible between Python, NodeJS and PHP
implementations.InstallationLatest stable version from PyPI:pipinstallskaor latest development version from GitHub.pipinstallhttps://github.com/barseghyanartur/ska/archive/main.tar.gzUsage examplesFor integration with Django, see theDjango integrationsection.Basic usagePure Python usage.Sender sideSigning URLs is as simple as follows.Required imports.fromskaimportsign_urlProducing a signed URL.signed_url=sign_url(auth_user='user',secret_key='your-secret_key',url='http://e.com/api/')GET http://e.com/api/?valid_until=1378045287.0&auth_user=user&signature=YlZpLFsjUKBalL4x5trhkeEgqE8%3DDefault lifetime of a signature is 10 minutes (600 seconds). If you want it
to be different, provide alifetimeargument tosign_urlfunction.Default name of the (GET) param holding the generated signature value
issignature. If you want it to be different, provide asignature_paramargument tosign_urlfunction.Default name of the (GET) param holding theauth_uservalue isauth_user. If you want it to be different, provide aauth_user_paramargument tosign_urlfunction.Default name of the (GET) param holding thevalid_untilvalue isvalid_until. If you want it to be different, provide avalid_until_paramargument tosign_urlfunction.Note, that by default a suffix ‘?’ is added after the givenurland
generated signature params. If you want that suffix to be custom, provide asuffixargument to thesign_urlfunction. If you want it to be gone,
set its’ value to empty string.With all customisations, it would look as follows:fromskaimportHMACSHA512Signature# Use HMAC SHA-512 algorithmsigned_url=sign_url(auth_user='user',secret_key='your-secret_key',lifetime=120,url='http://e.com/api/',signature_param='signature',auth_user_param='auth_user',valid_until_param='valid_until',signature_cls=HMACSHA512Signature)It’s also possible to add additional data to the signature by providing aextraargument (dict). Note, that additional data is signed as well.
If request is somehow tampered (values vary from originally provided ones),
signature becomes invalid.sign_url(auth_user='user',secret_key='your-secret_key',url='http://e.com/api/',extra={'email':'doe@example.com','last_name':'Doe','first_name':'Joe'})You may now proceed with the signed URL request. If you use the famousrequestslibrary, it would be as follows.importrequestsrequests.get(signed_url)If you want to use POST method instead, you would likely want to get a
dictionary back, in order to append it to the POST data later.Required imports.fromskaimportsignature_to_dictProducing a dictionary containing the signature data, ready to be put into
the request (for example POST) data. All customisations mentioned above for
thesign_urlfunction, also apply to thesignature_to_dict:signature_dict=signature_to_dict(auth_user='user',secret_key='your-secret_key'){
    'signature': 'YlZpLFsjUKBalL4x5trhkeEgqE8=',
    'auth_user': 'user',
    'valid_until': '1378045287.0'
}Adding of additional data to the signature works in the same way:signature_dict=signature_to_dict(auth_user='user',secret_key='your-secret_key',extra={'email':'john.doe@mail.example.com','first_name':'John','last_name':'Doe'}){
    'auth_user': 'user',
    'email': 'john.doe@mail.example.com',
    'extra': 'email,first_name,last_name',
    'first_name': 'John',
    'last_name': 'Doe',
    'signature': 'cnSoU/LnJ/ZhfLtDLzab3a3gkug=',
    'valid_until': 1387616469.0
}If you for some reason prefer a lower level implementation, read the same
section in theAdvanced usage (low-level)chapter.Recipient sideValidating the signed request data is as simple as follows.Required imports.fromskaimportvalidate_signed_request_dataValidating the signed request data. Note, thatdatavalue is expected to
be a dictionary;request.GETis given as an example. It will most likely
vary from what’s used in your framework (unless you use Django).validation_result=validate_signed_request_data(data=request.GET,# Note, that ``request.GET`` is given as example.secret_key='your-secret_key')Thevalidate_signed_request_dataproduces aska.SignatureValidationResultobject, which holds the following data.result(bool): True if data is valid. False otherwise.reason(list): List of strings, indicating validation errors. Empty list
in case ifresultis True.Default name of the (GET) param holding the signature value issignature.
If you want it to be different, provide asignature_paramargument tovalidate_signed_request_datafunction.Default name of the (GET) param holding theauth_uservalue isauth_user. If you want it to be different, provide aauth_user_paramargument tovalidate_signed_request_datafunction.Default name of the (GET) param holding thevalid_untilvalue isvalid_until. If you want it to be different, provide avalid_until_paramargument tovalidate_signed_request_datafunction.With all customisations, it would look as follows. Note, thatrequest.GETis given as example.fromskaimportHMACSHA256Signature# Use HMAC SHA-256 algorithmvalidation_result=validate_signed_request_data(data=request.GET,secret_key='your-secret_key',signature_param='signature',auth_user_param='auth_user',valid_until_param='valid_until',signature_cls=HMACSHA256Signature)If you for some reason prefer a lower level implementation, read the same
section in theAdvanced usage (low-level)chapter.Command line usageIt’s possible to generate a signed URL from command line using theska.generate_signed_urlmodule.Arguments:-h, --help            show this help message and exit

-au AUTH_USER, --auth-user AUTH_USER
                      `auth_user` value

-sk SECRET_KEY, --secret-key SECRET_KEY
                      `secret_key` value

-vu VALID_UNTIL, --valid-until VALID_UNTIL
                      `valid_until` value

-l LIFETIME, --lifetime LIFETIME
                      `lifetime` value

-u URL, --url URL     URL to sign

-sp SIGNATURE_PARAM, --signature-param SIGNATURE_PARAM
                      (GET) param holding the `signature` value

-aup AUTH_USER_PARAM, --auth-user-param AUTH_USER_PARAM
                      (GET) param holding the `auth_user` value

-vup VALID_UNTIL_PARAM, --valid-until-param VALID_UNTIL_PARAM
                      (GET) param holding the `auth_user` valueExample:ska-sign-url-auuser-skyour-secret-key--urlhttp://example.comAdvanced usage (low-level)Sender sideRequired imports.fromskaimportSignature,RequestHelperGenerate a signature.signature=Signature.generate_signature(auth_user='user',secret_key='your-secret-key')Default lifetime of a signature is 10 minutes (600 seconds). If you want it to
be different, provide alifetimeargument togenerate_signaturemethod.signature=Signature.generate_signature(auth_user='user',secret_key='your-secret-key',lifetime=120# Signature lifetime set to 120 seconds.)Adding of additional data to the signature works in the same way as insign_url.signature=Signature.generate_signature(auth_user='user',secret_key='your-secret-key',extra={'email':'doe@example.com','last_name':'Doe','first_name':'Joe'})For HMAC SHA-384 algorithm it would look as follows.fromskaimportHMACSHA384Signaturesignature=HMACSHA384Signature.generate_signature(auth_user='user',secret_key='your-secret-key')Your endpoint operates with certain param names and you need to wrap generated
signature params into the URL. In order to have the job done in an easy way,
create a request helper. Feed names of the (GET) params to the request helper
and let it make a signed endpoint URL for you.request_helper=RequestHelper(signature_param='signature',auth_user_param='auth_user',valid_until_param='valid_until')Append signature params to the endpoint URL.signed_url=request_helper.signature_to_url(signature=signature,endpoint_url='http://e.com/api/')GET http://e.com/api/?valid_until=1378045287.0&auth_user=user&signature=YlZpLFsjUKBalL4x5trhkeEgqE8%3DMake a request.importrequestsr=requests.get(signed_url)For HMAC SHA-384 algorithm it would look as follows.fromskaimportHMACSHA384Signaturerequest_helper=RequestHelper(signature_param='signature',auth_user_param='auth_user',valid_until_param='valid_until',signature_cls=HMACSHA384Signature)signed_url=request_helper.signature_to_url(signature=signature,endpoint_url='http://e.com/api/')Recipient sideRequired imports.fromskaimportRequestHelperCreate a request helper. Your endpoint operates with certain param names. In
order to have the job done in an easy way, we feed those params to the
request helper and let it extract data from signed request for us.request_helper=RequestHelper(signature_param='signature',auth_user_param='auth_user',valid_until_param='valid_until')Validate the request data. Note, thatrequest.GETis given just as an
example.validation_result=request_helper.validate_request_data(data=request.GET,secret_key='your-secret-key')Your implementation further depends on you, but may look as follows.ifvalidation_result.result:# Validated, proceed further# ...else:# Validation not passed.raiseHttp404(validation_result.reason)You can also just validate the signature by callingvalidate_signaturemethod of theska.Signature.Signature.validate_signature(signature='EBS6ipiqRLa6TY5vxIvZU30FpnM=',auth_user='user',secret_key='your-secret-key',valid_until='1377997396.0')Django integrationskacomes with Django model- and view-decorators for producing signed URLs
and and validating the endpoints, as well as with authentication backend,
which allows password-less login into Django web site usingskagenerated
signature tokens. There’s also a template tag for signing URLs.DemoIn order to be able to quickly evaluate theska, a demo app (with a quick
installer) has been created (works on Ubuntu/Debian, may work on other Linux
systems as well, although not guaranteed). Follow the instructions below for
having the demo running within a minute.Grab the latestska_example_app_installer.shand execute it:wget-O-https://raw.github.com/barseghyanartur/ska/stable/examples/ska_example_app_installer.sh|bashOpen your browser and test the app.Foo listing (ska protected views):URL:http://127.0.0.1:8001/foo/Authentication page (ska authentication backend):URL:http://127.0.0.1:8001/foo/authenticate/Django admin interface:URL:http://127.0.0.1:8001/admin/Admin username: test_adminAdmin password: testConfigurationSecret key (str) must be defined insettingsmodule of your project.SKA_SECRET_KEY='my-secret-key'The following variables can be overridden insettingsmodule of your
project.SKA_UNAUTHORISED_REQUEST_ERROR_MESSAGE(str): Plain text error message.
Defaults to “Unauthorised request. {0}”.SKA_UNAUTHORISED_REQUEST_ERROR_TEMPLATE(str): Path to 401 template that
should be rendered in case of 401
responses. Defaults to empty string (not provided).SKA_AUTH_USER(str): Theauth_userargument forska.sign_urlfunction. Defaults to “ska-auth-user”.See the workingexample project.Multiple secret keysImagine, you have a site to which you want to offer a password-less login for
various clients/senders and you don’t want them all to have one shared secret
key, but rather have their own one. Moreover, you specifically want to execute
very custom callbacks not only for each separate client/sender, but also for
different sort of users authenticating.┌────────────────┐
                          │ Site providing │
                          │ authentication │
                          │ ────────────── │
                          │ custom secret  │
                          │    keys per    │
                          │     client     │
                          │ ────────────── │
                          │ Site 1: 'sk-1' │
             ┌───────────>│ Site 2: 'sk-2' │<───────────┐
             │            │ Site 3: 'sk-3' │            │
             │      ┌────>│ Site 4: 'sk-4' │<────┐      │
             │      │     └────────────────┘     │      │
             │      │                            │      │
             │      │                            │      │
┌────────────┴─┐  ┌─┴────────────┐  ┌────────────┴─┐  ┌─┴────────────┐
│    Site 1    │  │    Site 2    │  │    Site 3    │  │    Site 4    │
│ ──────────── │  │ ──────────── │  │ ──────────── │  │ ──────────── │
│  secret key  │  │  secret key  │  │  secret key  │  │  secret key  │
│    'sk-1'    │  │    'sk-2'    │  │    'sk-3'    │  │    'sk-4'    │
└──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘In order to make the stated above possible, the concept of providers is
introduced. You can define a secret key, callbacks or redirect URL. See an
example below. Note, that keys of theSKA_PROVIDERS(“client_1”,
“client_2”, etc.) are the provider keys.SKA_PROVIDERS={# ********************************************************# ******************** Basic gradation *******************# ********************************************************# Site 1'client_1':{'SECRET_KEY':'sk-1',},# Site 2'client_2':{'SECRET_KEY':'sk-2',},# Site 3'client_3':{'SECRET_KEY':'sk-3',},# Site 4'client_4':{'SECRET_KEY':'sk-4',},# ********************************************************# ******* You make gradation as complex as you wish ******# ********************************************************# Client 1, group users'client_1.users':{'SECRET_KEY':'client-1-users-secret-key',},# Client 1, group power_users'client_1.power_users':{'SECRET_KEY':'client-1-power-users-secret-key','USER_CREATE_CALLBACK':'foo.ska_callbacks.client1_power_users_create',},# Client 1, group admins'client_1.admins':{'SECRET_KEY':'client-1-admins-secret-key','USER_CREATE_CALLBACK':'foo.ska_callbacks.client1_admins_create','REDIRECT_AFTER_LOGIN':'/admin/'},}See theCallbackssection for the list of callbacks. Note, that callbacks
defined in theSKA_PROVIDERSare overrides. If a certain callback isn’t
defined in theSKA_PROVIDERS, authentication backend falls back to the
respective default callback function.Obviously, server would have to have the full list of providers defined. On
the client side you would only have to store the general secret key and of
course the provider UID(s).When making a signed URL on the sender side, you should be providing theproviderkey in theextraargument. See the example below for how you
would do it forclient_1.power_users.fromskaimportsign_urlfromska.defaultsimportDEFAULT_PROVIDER_PARAMserver_ska_login_url='https://server-url.com/ska/login/'signed_remote_ska_login_url=sign_url(auth_user='test_ska_user',# Using provider-specific secret key. This value shall be equal to# the value of SKA_PROVIDERS['client_1.power_users']['SECRET_KEY'],# defined in your projects' Django settings module.secret_key='client-1-power-users-secret-key',url=server_ska_login_url,extra={'email':'test_ska_user@mail.example.com','first_name':'John','last_name':'Doe',# Using provider specific string. This value shall be equal to# the key string "client_1.power_users" of SKA_PROVIDERS,# defined in your projcts' Django settings module.DEFAULT_PROVIDER_PARAM:'client_1.power_users',})Django model method decoratorsign_urlThis is most likely be used in modulemodels(models.py).Imagine, you have a some objects listing and you want to protect the URLs to
be viewed by authorised parties only. You would then useget_signed_absolute_urlmethod when rendering the listing (HTML).fromdjango.dbimportmodelsfromdjango.utils.translationimportugettext_lazyas_fromdjango.core.urlresolversimportreversefromska.contrib.django.ska.decoratorsimportsign_urlclassFooItem(models.Model):title=models.CharField(_("Title"),max_length=100)slug=models.SlugField(unique=True,verbose_name=_("Slug"))body=models.TextField(_("Body"))# Unsigned absolute URL, which goes to the foo item detail page.defget_absolute_url(self):returnreverse('foo.detail',kwargs={'slug':self.slug})# Signed absolute URL, which goes to the foo item detail page.@sign_url()defget_signed_absolute_url(self):returnreverse('foo.detail',kwargs={'slug':self.slug})Note, thatsign_urldecorator accepts the following optional arguments.auth_user(str): Username of the user making the request.secret_key: The shared secret key. If set, overrides
theSKA_SECRET_KEYvariable set in thesettingsmodule of your
project.valid_until(floatorstr): Unix timestamp. If not given, generated
automatically (now + lifetime).lifetime(int): Signature lifetime in seconds.suffix(str): Suffix to add after theendpoint_urland before the
appended signature params.signature_param(str): Name of the GET param name which would hold the
generated signature value.auth_user_param(str): Name of the GET param name which would hold
theauth_uservalue.valid_until_param(str): Name of the GET param name which would hold
thevalid_untilvalue.Django view decoratorvalidate_signed_requestTo be used to protect views (file views.py). Should be applied to
views (endpoints) that require signed requests. If checks are not successful,
aska.contrib.django.ska.http.HttpResponseUnauthorizedis returned, which
is a subclass of Django’sdjango.http.HttpResponse. You can provide your
own template for 401 error. Simply point theSKA_UNAUTHORISED_REQUEST_ERROR_TEMPLATEinsettingsmodule to the right
template. Seeska/contrib/django/ska/templates/ska/401.htmlas a template
example.fromska.contrib.django.ska.decoratorsimportvalidate_signed_request# Your view that shall be protected@validate_signed_request()defdetail(request,slug,template_name='foo/detail.html'):# Your codeNote, thatvalidate_signed_requestdecorator accepts the following optional
arguments.secret_key(str) : The shared secret key. If set, overrides
theSKA_SECRET_KEYvariable  set in thesettingsmodule of your
project.signature_param(str): Name of the (for example GET or POST) param name
which holds thesignaturevalue.auth_user_param(str): Name of the (for example GET or POST) param name
which holds theauth_uservalue.valid_until_param(str): Name of the (foe example GET or POST) param
name which holds thevalid_untilvalue.If you’re using class based views, use them_validate_signed_requestdecorator instead ofvalidate_signed_request.Template tagsThere are two template tags modules:ska_tagsandska_constance_tags.
They are functionally identical, althoughska_constance_tagsis tied todjango-constance.For standard settings configurations, template tags shall be loaded as follows:{% load ska_tags %}Fordjango-constancebased settings configurations, template tags shall be
loaded as follows:{% load ska_constance_tags %}Note, that if you want to useska_constance_tags, add
theska.contrib.django.ska.integration.constance_integrationline to
your``INSTALLED_APPS``:INSTALLED_APPS=(# ...'ska.contrib.django.ska.integration.constance_integration',# ...)sign_urlThesign_urltemplate tag accepts template context and the following
params:urlauth_user: If not given,request.user.get_username()is used.secret_key: If not given, the secret key from settings is used.valid_until: If not given, calculated fromlifetime.lifetime: Defaults toska.defaults.SIGNATURE_LIFETIME.suffix: Defaults toska.defaults.DEFAULT_URL_SUFFIX.signature_param: Defaults toska.defaultsDEFAULT_SIGNATURE_PARAM.auth_user_param: Defaults toska.defaults.DEFAULT_AUTH_USER_PARAM.valid_until_param: Defaults toska.defaults.DEFAULT_VALID_UNTIL_PARAM.signature_cls: Defaults toska.signatures.Signature.Usage example:{% load ska_tags %}

{% for item in items%}

    {% sign_url item.get_absolute_url as item_signed_absolute_url %}<ahref="{{ item_signed_absolute_url }}">{{ item }}</a>{% endfor %}provider_sign_urlTheprovider_sign_urltemplate tag accepts template context and the
following params:urlprovider: Provider name.auth_user: If not given,request.user.get_username()is used.valid_until: If not given, calculated fromlifetime.lifetime: Defaults toska.defaults.SIGNATURE_LIFETIME.suffix: Defaults toska.defaults.DEFAULT_URL_SUFFIX.signature_param: Defaults toska.defaultsDEFAULT_SIGNATURE_PARAM.auth_user_param: Defaults toska.defaults.DEFAULT_AUTH_USER_PARAM.valid_until_param: Defaults toska.defaults.DEFAULT_VALID_UNTIL_PARAM.signature_cls: Defaults toska.signatures.Signature.fail_silently: Defaults to False.Usage example:{% load ska_tags %}

{% for item in items%}

    {% provider_sign_url url=item.get_absolute_url provider='client_1.users' as item_signed_absolute_url %}<ahref="{{ item_signed_absolute_url }}">{{ item }}</a>{% endfor %}Authentication backendsAllows you to get a password-less login to Django web site.At the moment there are two backends implemented:SkaAuthenticationBackend: Uses standard Django settings.SkaAuthenticationConstanceBackend: Relies on dynamic settings
functionality provided bydjango-constance.By default, number of logins using the same token is not limited. If you wish
that single tokens become invalid after first use, set the following variables
to True in your projects’ Django settings module.SKA_DB_STORE_SIGNATURES=TrueSKA_DB_PERFORM_SIGNATURE_CHECK=TrueSkaAuthenticationBackendSkaAuthenticationBackenduses standard Django settings.Recipient sideRecipient is the host (Django site), to which the sender tries to get
authenticated (log in). On the recipient side the following shall be present.settings.pyAUTHENTICATION_BACKENDS=('ska.contrib.django.ska.backends.SkaAuthenticationBackend','django.contrib.auth.backends.ModelBackend',)INSTALLED_APPS=(# ...'ska.contrib.django.ska',# ...)SKA_SECRET_KEY='secret-key'SKA_UNAUTHORISED_REQUEST_ERROR_TEMPLATE='ska/401.html'SKA_REDIRECT_AFTER_LOGIN='/foo/logged-in/'urls.pyurlpatterns=[url(r'^ska/',include('ska.contrib.django.ska.urls')),url(r'^admin/',include(admin.site.urls)),]CallbacksThere are several callbacks implemented for authentication backend.USER_VALIDATE_CALLBACK(str): Validate request callback. Created to
allow adding custom logic to the incoming authentication requests. The main
purpose is to provide a flexible way of raising exceptions if the incoming
authentication request shall be blocked (for instance, email or username is
in black-list or right the opposite - not in the white list). The only aim of
theUSER_VALIDATE_CALLBACKis to raise adjango.core.PermissionDeniedexception if request data is invalid. In that case authentication flow will
halt. All other exceptions would simply be ignored (but logged) and if no
exception raised, the normal flow would be continued.USER_GET_CALLBACK(str): Fired if user was successfully fetched from
database (existing user).USER_CREATE_CALLBACK(str): Fired right after user has been
created (user didn’t exist).USER_INFO_CALLBACK(str): Fired upon successful authentication.Example of a callback function (let’s say, it resides in modulemy_app.ska_callbacks):defmy_callback(user,request,signed_request_data)# Your code…where:userisdjango.contrib.auth.models.Userinstance.requestisdjango.http.HttpRequestinstance.signed_request_datais dictionary with signed request data.For example, if you need to assign user to some local Django group, you could
specify the group name on the client side (add it to theextradictionary)
and based on that, add the user to the group in the callback.The callback is a path qualifier of the callback function. Considering the
example above, it would bemy_app.ska_callbacks.my_callback.Prefix names of each callback variable withSKA_in your projects’ settings
module.Example:SKA_USER_GET_CALLBACK='my_app.ska_callbacks.my_get_callback'SKA_USER_CREATE_CALLBACK='my_app.ska_callbacks.my_create_callback'Sender sideSender is the host (another Django web site) from which users authenticate to
the Recipient using signed URLs.On the sender side, the only thing necessary to be present is theskamodule for Django and of course the sameSECRET_KEYas on the server side.
Further, the serverskalogin URL (in our case “/ska/login/”) shall be
signed usingska(for example, usingsign_urlfunction). Theauth_userparam would be used as a Django username. See the example below.fromskaimportsign_urlfromska.contrib.django.ska.settingsimportSECRET_KEYserver_ska_login_url='https://server-url.com/ska/login/'signed_url=sign_url(auth_user='test_ska_user_0',secret_key=SECRET_KEY,url=server_ska_login_url,extra={'email':'john.doe@mail.example.com','first_name':'John','last_name':'Doe',})Note, that youextradictionary is optional! Ifemail,first_nameandlast_namekeys are present, upon successful validation, the data
would be saved into users’ profile.Put this code, for instance, in your view and then make the generated URL
available in template context and render it as a URL so that user can click
on it for authenticating to the server.defauth_to_server(request,template_name='auth_to_server.html'):# Some code + obtaining the `signed_url` (code shown above)context={'signed_url':signed_url}returnrender(request,template_name,context)SkaAuthenticationConstanceBackendRelies on dynamic settings functionality provided bydjango-constance.Only differences with `SkaAuthenticationBackend` are mentioned.NoteAdditional requirements shall be installed. See theconstance.txtfile for additional requirements (django-constance,django-json-widget,django-picklefield,jsonfield2andredis).settings.pyAUTHENTICATION_BACKENDS=('ska.contrib.django.ska.backends.constance_backend.SkaAuthenticationConstanceBackend','django.contrib.auth.backends.ModelBackend',)INSTALLED_APPS=(# ...'constance',# django-constance'ska.contrib.django.ska','django_json_widget',# For nice admin JSON widget# ...)CONSTANCE_CONFIG={'SKA_PROVIDERS':({},# The default value'JSON data',# Help text in admin'JSONField_config',# Field config)}CONSTANCE_ADDITIONAL_FIELDS={'JSONField_config':[# `jsonfield2` package might be used for storing the JSON field,# however, at the moment of writing it has a bug which makes# the JSON invalid after the first save. To avoid that, it has# been patched and resides in examples/simple/jsonfield2_addons/# module.'jsonfield2_addons.forms.JSONField',{'widget':'django_json_widget.widgets.JSONEditorWidget',}],}CONSTANCE_BACKEND='constance.backends.redisd.RedisBackend'CONSTANCE_REDIS_CONNECTION={'host':'localhost','port':6379,'db':0,}NoteIn very tiny bits, although not required, thejsonfield2anddjango-json-widgetpackages are used for editing of theSKA_PROVIDERSsetting in Django
admin.NoteIn the example shown above, theRedisBackendofdjango-constanceis used. You could also useDatabaseBackend. Study thedocumentationfor more.NoteIf yourSKA_PROVIDERSsettings are stored in the constance asstrinstead ofdict, set the settingSKA_CONSTANCE_SETTINGS_PARSE_FROM_JSONtoTrue.WithDatabaseBackendit would look as follows:CONSTANCE_BACKEND='constance.backends.database.DatabaseBackend'INSTALLED_APPS=(# ...'constance.backends.database',# ...)Quick demo of the dynamic backendClone this project:gitclonegit@github.com:barseghyanartur/ska.gitInstall/migrate:./scripts/install.shpipinstall-rexamples/requirements/django_2_1.txt./scripts/migrate.sh--settings=settings.constance_settingsRun:./scripts/runserver.sh--settings=settings.constance_settingsGo tohttp://localhost:8000/admin/constance/config/.Paste the following code:{"client_1.users":{"SECRET_KEY":"client-1-users-secret-key"},"client_1.power_users":{"SECRET_KEY":"client-1-power-users-secret-key","USER_CREATE_CALLBACK":"foo.ska_callbacks.client1_power_users_create"},"client_1.admins":{"SECRET_KEY":"client-1-admins-secret-key","USER_CREATE_CALLBACK":"foo.ska_callbacks.client1_admins_create","USER_GET_CALLBACK":"foo.ska_callbacks.client1_admins_get","USER_INFO_CALLBACK":"foo.ska_callbacks.client1_admins_info_constance","REDIRECT_AFTER_LOGIN":"/admin/auth/user/"}}Openhttp://localhost:8000/foo/authenticate/in another browser and navigate
to theLog in - client_1.adminslink in theSuccesstable column of
theBy providersection. Upon clicking, you should be logged in.
You have used the dynamic settings.urls.pydjango-constancespecific views and urls are used. Seeska.contrib.django.ska.views.constance_viewsandska.contrib.django.ska.urls.constance_urlsfor the reference.urlpatterns=[url(r'^ska/',include('ska.contrib.django.ska.urls.constance_urls')),url(r'^admin/',include(admin.site.urls)),]Custom authentication backendTo implement alternative authentication backend, see the following example:fromconstanceimportconfigfromska.contrib.django.backendsimportBaseSkaAuthenticationBackendclassSkaAuthenticationConstanceBackend(BaseSkaAuthenticationBackend):"""Authentication backend."""defget_settings(self):"""

        :return:
        """returnconfig.SKA_PROVIDERSThat’s it. The only thing theget_settingsmethod shall return isdictwith providers data (see theMultiple secret keysfor the reference;
return value of theget_settings` is ``SKA_PROVIDERSdict).Purging of old signature dataIf you have lots of visitors and theSKA_DB_STORE_SIGNATURESset to True,
your database grows. If you wish to get rid of old signature token data, you
may want to execute the following command using a cron job../manage.pyska_purge_stored_signature_dataSecurity notesFrom point of security, you should be serving the following pages via HTTP
secure connection:The server login page (/ska/login/).The client page containing the authentication links.Django REST Framework integrationPermission classesFor protecting views without actually being authenticated into the system,
specific permission classes are implemented (for both plan settings and
provider settings, as well as both plain- and provider-settings work in
combination withdjango-constancepackage).The following permission classes are implemented:SignedRequestRequiredProviderSignedRequestRequiredConstanceSignedRequestRequiredConstanceProviderSignedRequestRequiredProviderSignedRequestRequired examplefromrest_framework.viewsetsimportModelViewSetfromska.contrib.django.ska.integration.drf.permissionsimport(ProviderSignedRequestRequired)from.modelsimportFooItemfrom.serializersimportFooItemSerializerclassFooItemViewSet(ModelViewSet):"""FooItem model viewset."""permission_classes=(ProviderSignedRequestRequired,)queryset=FooItem.objects.all()serializer_class=FooItemSerializerSigning requestsRequests are signed the same way. Sample code:# Given that we have `auth_user`, `auth_user_email`, `provider_name`# (and the rest), the code would look as follows:fromskaimportsign_urlfromska.defaultsimportDEFAULT_PROVIDER_PARAMextra={'email':auth_user_email,'first_name':first_name,'last_name':last_name,}ifprovider_name:extra.update({DEFAULT_PROVIDER_PARAM:provider_name})signed_url=sign_url(auth_user=auth_user,secret_key=secret_key,url=url,extra=extra)JWT tokens for authenticationFor obtaining JWT tokens for authentication. Also works withdjango-constance.settings exampleREST_FRAMEWORK={'DEFAULT_AUTHENTICATION_CLASSES':('rest_framework_jwt.authentication.JSONWebTokenAuthentication','rest_framework.authentication.SessionAuthentication','rest_framework.authentication.BasicAuthentication',),}urls exampleurlpatterns=[# ...url(r'^ska-rest/',include('ska.contrib.django.ska.integration.drf.urls.jwt_token')),]Sample requesthttp://localhost:8008/ska-rest/obtain-jwt-token/
    ?signature=P92KWDDe0U84Alvu0tvmYoi8e8s%3D
    &auth_user=test_ska_user
    &valid_until=1548195246.0
    &extra=email%2Cfirst_name%2Clast_name
    &email=test_ska_user%40mail.example.com
    &first_name=John
    &last_name=DoeSample responseHTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept{"token":"eyJ0eXAiO.eyJ1c2VyX2lkIjo.m_saOvyKBO3"}TestingSimply type:pytestOr use tox:toxOr use tox to check specific env:tox-epy39Or run Django tests:pythonexamples/simple/manage.pytestska--settings=settings.testingWriting documentationKeep the following hierarchy.=====
title
=====

header
======

sub-header
----------

sub-sub-header
~~~~~~~~~~~~~~

sub-sub-sub-header
++++++++++++++++++

sub-sub-sub-sub-header
^^^^^^^^^^^^^^^^^^^^^^

sub-sub-sub-sub-sub-header
**************************LicenseGPL-2.0-only OR LGPL-2.1-or-laterSupportFor any issues contact me at the e-mail given in theAuthorsection.AuthorArtur Barseghyan <artur.barseghyan@gmail.com>