Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true

# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
# python:
# install:
# - requirements: docs/requirements.txt
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
ARG PYVERSION=3.9.19-bullseye

FROM python:${PYVERSION} AS dev

WORKDIR /app

COPY requirements.txt /app/

RUN apt-get update \
&& apt-get install -q -y \
jq \
&& apt-get clean

RUN pip install -r requirements.txt

FROM dev as prod

COPY ./ /app/


47 changes: 47 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pipeline {
agent {
label "python"
}
stages {
stage('Virtualenv'){
steps {
sh '/usr/bin/virtualenv toxtest -p /usr/bin/python3'
sh 'toxtest/bin/pip install tox==3.28.0 pathlib2'
}
}
stage('Test'){
parallel {
stage('Unit Test Django 3.1'){
steps {
sh 'toxtest/bin/tox -e py3.8-django{3.1}'
}
}
stage('Unit Test Django 3.2'){
steps {
sh 'toxtest/bin/tox -e py3.8-django{3.2}'
}
}
stage('Unit Test Django 4.0'){
steps {
sh 'toxtest/bin/tox -e py3.8-django{4.0}'
}
}
stage('Unit Test Django 4.1'){
steps {
sh 'toxtest/bin/tox -e py3.8-django{4.1}'
}
}
stage('Unit Test Django 4.2'){
steps {
sh 'toxtest/bin/tox -e py3.8-django{4.2}'
}
}
}
}
}
post {
cleanup {
cleanWs()
}
}
}
11 changes: 10 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
django-oauth2
======================

.. image:: https://travis-ci.org/stormsherpa/django-oauth2-provider.png?branch=master
This copy of the repository has been retired! It contains the pull request and release history
since it was forked from caffeinehit/django-oauth2-provider. The current authoritative copy of
this repo is in stormsherpa/django-oauth2-provider.

*django-oauth2* is a Django application that provides
customizable OAuth2\-authentication for your Django projects.
Expand All @@ -12,3 +14,10 @@ License
=======

*django-oauth2* is a fork of *django-oauth2-provider* which is released under the MIT License. Please see the LICENSE file for details.


Packaging
=========

$ python -m build

68 changes: 68 additions & 0 deletions aws_identity_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import os
import sys
import json

from datetime import datetime
from urllib import request, error
import requests

import boto3
# aws-v4-signature==2.0
from awsv4sign import generate_http11_header

service = 'sts'
region = 'us-west-2'

session = boto3.Session()
creds = session.get_credentials()
access_key = creds.access_key
secret_key = creds.secret_key
session_token = creds.token

print(f"access_key: {access_key[:10]}<redacted...>")
print(f"secret_key: {secret_key[:10]}<redacted...>")
print(f"session_token: {session_token[:20]}<redacted...>")
print(f"profile: {os.environ.get('AWS_PROFILE')}")

url = 'https://sts.{region}.amazonaws.com/'.format(region=region)
httpMethod = 'post'
canonicalHeaders = {
'host': f'sts.{region}.amazonaws.com',
'x-amz-date': datetime.utcnow().strftime('%Y%m%dT%H%M%SZ'),
'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
}
if session_token:
canonicalHeaders['x-amz-security-token'] = session_token

payload_str = "Action=GetCallerIdentity&Version=2011-06-15"

headers = generate_http11_header(
service, region, access_key, secret_key,
url, 'post', canonicalHeaders, {},
'', payload_str
)

token_request_args = {
"grant_type": "aws_identity",
"region": region,
"post_body": payload_str,
"headers_json": json.dumps(headers),
}
print(payload_str)
print(json.dumps(headers, indent=4))

req = request.Request("https://sts.us-west-2.amazonaws.com/", data=payload_str.encode('utf-8'), headers=headers, method='POST')
try:
response = request.urlopen(req)
print(f"Local request test result: {response.read()}")
except error.HTTPError as e:
print(f"HTTPError: {e}: {e.fp.read()}")
sys.exit(1)

print("Attempting access_token grant request with same signed request:\n")

token_response = requests.post("http://localhost:8000/oauth2/access_token",
data=token_request_args)
token_info = token_response.json()

print(token_info)
24 changes: 24 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

services:
test:
build:
context: .
target: dev
user: ${UID}
volumes:
- ${WORKSPACE:-.}:/app
environment:
- DJANGO_SETTINGS_MODULE=tests.settings

web:
build:
context: .
target: dev
user: ${UID}
volumes:
- ${WORKSPACE:-.}:/app
ports:
- "8000:8000"
environment:
- DJANGO_SETTINGS_MODULE=tests.settings
# entrypoint: [ "python3", "manage.py", "runserver" ]
9 changes: 9 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
v 4.1
-----
* Add aws_identity grant_type
* Update for Django 3.1-4.2

v 4.0
-----
* Update for Django 3.0-4.1

v 2.4
-----
* Add HTTP Authorization Bearer token support to Oauth2UserMiddleware
Expand Down
23 changes: 22 additions & 1 deletion docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Add :attr:`provider.oauth2.urls` to your root ``urls.py`` file.

::

url(r'^oauth2/', include('provider.oauth2.urls', namespace = 'oauth2')),
path('oauth2/', include(('provider.oauth2.urls', 'oauth2'))),


.. note:: The namespace argument is required.
Expand Down Expand Up @@ -92,6 +92,27 @@ in :rfc:`4`.
.. note:: Remember that you should always use HTTPS for all your OAuth
2 requests otherwise you won't be secured.

Request an Access Token using AWS credentials
---------------------------------------------

The new aws_identity grant_type uses the parameters for a signed GetCallerIdentity
request to prove the caller's identity.

Your client needs to submit a :attr:`POST` request to
:attr:`/oauth2/access_token` including the following parameters:

* ``region`` - AWS Region
* ``post_body`` - The post body used for signing the request. Usually ``Action=GetCallerIdentity&Version=2011-06-15``
* ``headers_json`` - The headers produced by the AWSv4 signing process

The region value is used to produce the standard https://sts.(region).amazonaws.com/ url used to
make the GetCallerIdentity request. The URL is generated server side to reduce the risk of an
attack based on sending an improperly crafted full URL.

The aws-v4-signature library implements awsv4sign.generate_http11_header(). An example is
presented in the root of the repository in aws_identity_examply.py.


Integrate with Django Authentication
####################################

Expand Down
3 changes: 2 additions & 1 deletion provider/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
__version__ = "3.2"
__version__ = "4.3"
# The major version is expected to follow the current django major version:q
4 changes: 3 additions & 1 deletion provider/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

CONFIDENTIAL = 0
PUBLIC = 1
PKCE = 2

CLIENT_TYPES = (
(CONFIDENTIAL, "Confidential (Web applications)"),
(PUBLIC, "Public (Native and JS applications)")
(PUBLIC, "Public (Native and JS applications)"),
(PKCE, "RFC7636 PKCE (Native, JS, and Web applications)"),
)

RESPONSE_TYPE_CHOICES = getattr(settings, 'OAUTH_RESPONSE_TYPE_CHOICES', ("code", "token"))
Expand Down
6 changes: 6 additions & 0 deletions provider/oauth2/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ class AuthorizedClientAdmin(admin.ModelAdmin):
raw_id_fields = ('user',)


class AwsAccountAdmin(admin.ModelAdmin):
list_display = ('arn', 'client', 'max_token_lifetime')
raw_id_fields = ('acting_user',)


admin.site.register(models.AccessToken, AccessTokenAdmin)
admin.site.register(models.Grant, GrantAdmin)
admin.site.register(models.Client, ClientAdmin)
admin.site.register(models.AuthorizedClient, AuthorizedClientAdmin)
admin.site.register(models.AwsAccount, AwsAccountAdmin)
admin.site.register(models.RefreshToken)
admin.site.register(models.Scope)
3 changes: 3 additions & 0 deletions provider/oauth2/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ class Oauth2(AppConfig):
name = 'provider.oauth2'
label = 'oauth2'
verbose_name = "Provider Oauth2"

def ready(self):
import provider.oauth2.signals
31 changes: 24 additions & 7 deletions provider/oauth2/backends.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import base64

from provider.utils import now
from provider.oauth2.forms import ClientAuthForm, PublicPasswordGrantForm, PublicClientForm
from provider.oauth2.forms import ClientAuthForm, PublicPasswordGrantForm, PublicClientForm, PkceClientAuthForm
from provider.oauth2.models import AccessToken


class BaseBackend(object):
class BaseBackend:
"""
Base backend used to authenticate clients as defined in :rfc:`1` against
our database.
Expand All @@ -18,7 +18,7 @@ def authenticate(self, request=None):
pass


class BasicClientBackend(object):
class BasicClientBackend:
"""
Backend that tries to authenticate a client through HTTP authorization
headers as defined in :rfc:`2.3.1`.
Expand Down Expand Up @@ -47,7 +47,7 @@ def authenticate(self, request=None):
return None


class RequestParamsClientBackend(object):
class RequestParamsClientBackend:
"""
Backend that tries to authenticate a client through request parameters
which might be in the request body or URI as defined in :rfc:`2.3.1`.
Expand All @@ -68,7 +68,24 @@ def authenticate(self, request=None):
return None


class PublicPasswordBackend(object):
class PkceRequestParamsClientBackend:
def authenticate(self, request=None):
if request is None:
return None

if hasattr(request, 'REQUEST'):
args = request.REQUEST
else:
args = request.POST or request.GET
form = PkceClientAuthForm(args)

if form.is_valid():
return form.cleaned_data.get('client')

return None


class PublicPasswordBackend:
"""
Backend that tries to authenticate a client using username, password
and client ID. This is only available in specific circumstances:
Expand All @@ -93,7 +110,7 @@ def authenticate(self, request=None):
return None


class PublicClientBackend(object):
class PublicClientBackend:
def authenticate(self, request=None):
if request is None:
return None
Expand All @@ -110,7 +127,7 @@ def authenticate(self, request=None):
return None


class AccessTokenBackend(object):
class AccessTokenBackend:
"""
Authenticate a user via access token and client object.
"""
Expand Down
Loading