Skip to content

Commit

Permalink
Add oauth2 provider application
Browse files Browse the repository at this point in the history
  • Loading branch information
john-westcott-iv committed Feb 12, 2024
1 parent 91ab486 commit e4d7512
Show file tree
Hide file tree
Showing 25 changed files with 911 additions and 10 deletions.
22 changes: 14 additions & 8 deletions ansible_base/lib/abstract_models/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ def save(self, *args, warn_nonexistent_system_user=True, **kwargs):
update_fields = list(kwargs.get('update_fields', []))
user = self._attributable_user(warn_nonexistent_system_user)

# Encrypt any fields
from ansible_base.lib.utils.encryption import ansible_encryption

for field in self.encrypted_fields:
field_value = getattr(self, field, None)
if field_value:
setattr(self, field, ansible_encryption.encrypt_string(field_value))

# If the updating fields lists matches the objects `not_user_modified_fields` then we can just save and return
if update_fields == getattr(self, 'not_user_modified_fields', []):
logger.debug("Skipping modified field update because update_fields only contain not user modified fields")
super().save(*args, **kwargs)
return

# Manually perform auto_now_add and auto_now logic.
now = timezone.now()
if not self.pk and not self.created_on:
Expand All @@ -110,14 +124,6 @@ def save(self, *args, warn_nonexistent_system_user=True, **kwargs):
update_fields.append('modified_on')
update_fields.append('modified_by')

# Encrypt any fields
from ansible_base.lib.utils.encryption import ansible_encryption

for field in self.encrypted_fields:
field_value = getattr(self, field, None)
if field_value:
setattr(self, field, ansible_encryption.encrypt_string(field_value))

super().save(*args, **kwargs)

@classmethod
Expand Down
31 changes: 31 additions & 0 deletions ansible_base/lib/dynamic_config/dynamic_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,34 @@
SOCIAL_AUTH_STORAGE = "ansible_base.authentication.social_auth.AuthenticatorStorage"
SOCIAL_AUTH_STRATEGY = "ansible_base.authentication.social_auth.AuthenticatorStrategy"
SOCIAL_AUTH_LOGIN_REDIRECT_URL = "/"

if 'ansible_base.oauth2_provider' in INSTALLED_APPS: # noqa: F821
if 'oauth2_provider' not in INSTALLED_APPS: # noqa: F821
INSTALLED_APPS.append('oauth2_provider') # noqa: F821

try:
OAUTH2_PROVIDER # noqa: F821
except NameError:
OAUTH2_PROVIDER = {}

if 'ACCESS_TOKEN_EXPIRE_SECONDS' not in OAUTH2_PROVIDER:
OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] = 31536000000
if 'AUTHORIZATION_CODE_EXPIRE_SECONDS' not in OAUTH2_PROVIDER:
OAUTH2_PROVIDER['AUTHORIZATION_CODE_EXPIRE_SECONDS'] = 600
if 'REFRESH_TOKEN_EXPIRE_SECONDS' not in OAUTH2_PROVIDER:
OAUTH2_PROVIDER['REFRESH_TOKEN_EXPIRE_SECONDS'] = 2628000

OAUTH2_PROVIDER['APPLICATION_MODEL'] = 'dab_oauth2_provider.OAuth2Application'
OAUTH2_PROVIDER['ACCESS_TOKEN_MODEL'] = 'dab_oauth2_provider.OAuth2AccessToken'

oauth2_authentication_class = 'ansible_base.oauth2_provider.authentication.LoggedOAuth2Authentication'
if 'DEFAULT_AUTHENTICATION_CLASSES' not in REST_FRAMEWORK: # noqa: F821
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = [] # noqa: F821
if oauth2_authentication_class not in REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES']: # noqa: F821
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].insert(0, oauth2_authentication_class) # noqa: F821

# These have to be defined for the migration to function
OAUTH2_PROVIDER_APPLICATION_MODEL = 'dab_oauth2_provider.OAuth2Application'
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = 'dab_oauth2_provider.OAuth2AccessToken'
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "dab_oauth2_provider.OAuth2RefreshToken"
OAUTH2_PROVIDER_ID_TOKEN_MODEL = "dab_oauth2_provider.OAuth2IDToken"
Empty file.
3 changes: 3 additions & 0 deletions ansible_base/oauth2_provider/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin # noqa: F401

# Register your models here.
7 changes: 7 additions & 0 deletions ansible_base/oauth2_provider/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig


class Oauth2ProviderConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'ansible_base.oauth2_provider'
label = 'dab_oauth2_provider'
20 changes: 20 additions & 0 deletions ansible_base/oauth2_provider/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging

from django.utils.encoding import smart_str
from oauth2_provider.contrib.rest_framework import OAuth2Authentication

logger = logging.getLogger('ansible_base.oauth2_provider.authentication')


class LoggedOAuth2Authentication(OAuth2Authentication):
def authenticate(self, request):
ret = super().authenticate(request)
if ret:
user, token = ret
username = user.username if user else '<none>'
logger.info(
smart_str(u"User {} performed a {} to {} through the API using OAuth 2 token {}.".format(username, request.method, request.path, token.pk))
)
# TODO: check oauth_scopes when we have RBAC in Gateway
setattr(user, 'oauth_scopes', [x for x in token.scope.split() if x])
return ret
276 changes: 276 additions & 0 deletions ansible_base/oauth2_provider/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
# Generated by Django 4.2.8 on 2024-02-11 20:16

import re
import uuid

import django.core.validators
import django.db.models.deletion
import oauth2_provider.generators
from django.conf import settings
from django.db import migrations, models

import ansible_base.oauth2_provider.models.application


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
migrations.swappable_dependency(settings.ANSIBLE_BASE_ORGANIZATION_MODEL),
]

run_before = [
('oauth2_provider', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='OAuth2Application',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(default=None, editable=False, help_text='The date/time this resource was created')),
('modified_on', models.DateTimeField(default=None, editable=False, help_text='The date/time this resource was created')),
('name', models.CharField(help_text='The name of this resource', max_length=512)),
('client_id', models.CharField(db_index=True, default=oauth2_provider.generators.generate_client_id, max_length=100, unique=True)),
('description', models.TextField(blank=True, default='')),
('logo_data', models.TextField(default='', editable=False, validators=[django.core.validators.RegexValidator(re.compile('.*'))])),
('client_secret', ansible_base.oauth2_provider.models.application.OAuth2ClientSecretField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, help_text='Used for more stringent verification of access to an application when creating a token.', max_length=1024)),
('client_type', models.CharField(choices=[('confidential', 'Confidential'), ('public', 'Public')], help_text='Set to Public or Confidential depending on how secure the client device is.', max_length=32)),
('skip_authorization', models.BooleanField(default=False, help_text='Set True to skip authorization step for completely trusted applications.')),
('authorization_grant_type', models.CharField(choices=[('authorization-code', 'Authorization code'), ('password', 'Resource owner password-based')], help_text='The Grant type the user must use for acquire tokens for this application.', max_length=32)),
('created_by', models.ForeignKey(default=None, editable=False, help_text='The user who created this resource', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='%(app_label)s_%(class)s_created+', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(default=None, editable=False, help_text='The user who last modified this resource', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='%(app_label)s_%(class)s_modified+', to=settings.AUTH_USER_MODEL)),
('organization', models.ForeignKey(help_text='Organization containing this application.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='applications', to=settings.ANSIBLE_BASE_ORGANIZATION_MODEL)),
],
options={
'verbose_name': 'application',
'ordering': ('organization', 'name'),
'swappable': 'OAUTH2_PROVIDER_APPLICATION_MODEL',
'unique_together': {('name', 'organization')},
},
),
migrations.CreateModel(
name='OAuth2IDToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(default=None, editable=False, help_text='The date/time this resource was created')),
('modified_on', models.DateTimeField(default=None, editable=False, help_text='The date/time this resource was created')),
('created_by', models.ForeignKey(default=None, editable=False, help_text='The user who created this resource', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='%(app_label)s_%(class)s_created+', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(default=None, editable=False, help_text='The user who last modified this resource', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='%(app_label)s_%(class)s_modified+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'id token',
'swappable': 'OAUTH2_PROVIDER_ID_TOKEN_MODEL',
},
),
migrations.CreateModel(
name='OAuth2RefreshToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(default=None, editable=False, help_text='The date/time this resource was created')),
('modified_on', models.DateTimeField(default=None, editable=False, help_text='The date/time this resource was created')),
('created_by', models.ForeignKey(default=None, editable=False, help_text='The user who created this resource', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='%(app_label)s_%(class)s_created+', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(default=None, editable=False, help_text='The user who last modified this resource', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='%(app_label)s_%(class)s_modified+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'access token',
'ordering': ('id',),
'swappable': 'OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL',
},
),
migrations.CreateModel(
name='OAuth2AccessToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(default=None, editable=False, help_text='The date/time this resource was created')),
('modified_on', models.DateTimeField(default=None, editable=False, help_text='The date/time this resource was created')),
('description', models.TextField(blank=True, default='')),
('last_used', models.DateTimeField(default=None, editable=False, null=True)),
('scope', models.CharField(blank=True, choices=[('read', 'read'), ('write', 'write')], default='write', help_text="Allowed scopes, further restricts user's permissions. Must be a simple space-separated string with allowed scopes ['read', 'write'].", max_length=32)),
('created_by', models.ForeignKey(default=None, editable=False, help_text='The user who created this resource', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='%(app_label)s_%(class)s_created+', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(default=None, editable=False, help_text='The user who last modified this resource', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='%(app_label)s_%(class)s_modified+', to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(blank=True, help_text='The user representing the token owner', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'access token',
'ordering': ('id',),
'swappable': 'OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL',
},
),
migrations.AddField(
model_name='oauth2accesstoken',
name='application',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
),
migrations.AddField(
model_name='oauth2accesstoken',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2accesstoken',
name='expires',
field=models.DateTimeField(default=''),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2accesstoken',
name='token',
field=models.CharField(default='', max_length=255, unique=True),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2accesstoken',
name='updated',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='oauth2application',
name='algorithm',
field=models.CharField(blank=True, choices=[('', 'No OIDC support'), ('RS256', 'RSA with SHA-2 256'), ('HS256', 'HMAC with SHA-2 256')], default='', max_length=5),
),
migrations.AddField(
model_name='oauth2application',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2application',
name='post_logout_redirect_uris',
field=models.TextField(blank=True, help_text='Allowed Post Logout URIs list, space separated'),
),
migrations.AddField(
model_name='oauth2application',
name='redirect_uris',
field=models.TextField(blank=True, help_text='Allowed URIs list, space separated'),
),
migrations.AddField(
model_name='oauth2application',
name='updated',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='oauth2application',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='oauth2idtoken',
name='application',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
),
migrations.AddField(
model_name='oauth2idtoken',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2idtoken',
name='expires',
field=models.DateTimeField(default=''),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2idtoken',
name='jti',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='JWT Token ID'),
),
migrations.AddField(
model_name='oauth2idtoken',
name='scope',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='oauth2idtoken',
name='updated',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='oauth2idtoken',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='oauth2refreshtoken',
name='access_token',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='refresh_token', to=settings.OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL),
),
migrations.AddField(
model_name='oauth2refreshtoken',
name='application',
field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2refreshtoken',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2refreshtoken',
name='revoked',
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name='oauth2refreshtoken',
name='token',
field=models.CharField(default='', max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2refreshtoken',
name='updated',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='oauth2refreshtoken',
name='user',
field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
migrations.AlterField(
model_name='oauth2accesstoken',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='oauth2application',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='oauth2application',
name='name',
field=models.CharField(blank=True, max_length=255),
),
migrations.AlterField(
model_name='oauth2idtoken',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='oauth2refreshtoken',
name='id',
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterUniqueTogether(
name='oauth2refreshtoken',
unique_together={('token', 'revoked')},
),
migrations.AddField(
model_name='oauth2accesstoken',
name='id_token',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='access_token', to=settings.OAUTH2_PROVIDER_ID_TOKEN_MODEL),
),
migrations.AddField(
model_name='oauth2accesstoken',
name='source_refresh_token',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='refreshed_access_token', to=settings.OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL),
),
]
Empty file.
Loading

0 comments on commit e4d7512

Please sign in to comment.