Skip to content

Commit

Permalink
merge + removed debugger and made button for implementing the compone…
Browse files Browse the repository at this point in the history
…nt's statements dependent on request being set to approve
  • Loading branch information
SergioJFalcon committed May 11, 2022
2 parents 20fee61 + 183f64a commit 05891ea
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ export const ProposalSteps = ({ userId, system, element, proposal, request, hasS
}

const addComponentStatements = async () => {
debugger;
ajax_with_indicator({
url: `/systems/${parseInt(system.id)}/components/add_system_component`,
method: "POST",
Expand Down Expand Up @@ -244,7 +243,7 @@ export const ProposalSteps = ({ userId, system, element, proposal, request, hasS
<div><h2>Approval</h2></div>
<div>The confirmation fo system using {element.name} from component owner. System can proceed to use the component.</div>
<div style={{float: 'right'}}>
<Button variant="contained" onClick={addComponentStatements}>Add Selected Componenet</Button>
{getStatusLevel(proposal.status) === 3 && hasSentRequest === true &&<Button variant="contained" onClick={addComponentStatements}>Add Selected Componenet</Button>}
</div>
</Grid>
</Grid>
Expand Down
12 changes: 7 additions & 5 deletions siteapp/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import django.contrib.auth.admin as contribauthadmin
from jsonfield import JSONField
from .models import User, Organization, OrganizationalSetting, Folder, Invitation, Project, ProjectMembership, Portfolio, Support, \
Tag, Asset, ProjectAsset
Tag, Role, Party, Asset, ProjectAsset
from notifications.models import Notification


Expand Down Expand Up @@ -194,13 +194,13 @@ class TagAdmin(admin.ModelAdmin):
readonly_fields = ('id',)

class RoleAdmin(admin.ModelAdmin):
list_display = ('role_id', 'short_name')
fields = ('uuid', 'title', 'short_name', 'description')
readonly_fields = ('uuid',)
list_display = ('id', 'role_id', 'short_name')
fields = ('role_id', 'short_name', 'description')
readonly_fields = ('id',)

class PartyAdmin(admin.ModelAdmin):
list_display = ('uuid', 'name')
fields = ('uuid', 'party_type', 'name', 'short_name')
fields = ('uuid', 'party_type', 'name', 'short_name', 'email', 'phone_number', 'mobile_phone', 'user')
readonly_fields = ('uuid',)

class ProjectAssetAdmin(admin.ModelAdmin):
Expand All @@ -227,4 +227,6 @@ class ProjectAssetAdmin(admin.ModelAdmin):
admin.site.register(Notification, NotificationAdmin)
admin.site.register(Support, SupportAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Role, RoleAdmin)
admin.site.register(Party, PartyAdmin)
admin.site.register(ProjectAsset, ProjectAssetAdmin)
123 changes: 108 additions & 15 deletions siteapp/authentication/OIDCAuthentication.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import time
from urllib.parse import urlencode
import json

from django.conf import settings
from django.core.exceptions import SuspiciousOperation
Expand All @@ -9,37 +10,129 @@
from mozilla_django_oidc.auth import OIDCAuthenticationBackend, LOGGER
from mozilla_django_oidc.middleware import SessionRefresh
from mozilla_django_oidc.utils import absolutify, add_state_and_nonce_to_session
from base64 import urlsafe_b64encode, urlsafe_b64decode

from siteapp.models import Portfolio, User

from siteapp.models import Portfolio


class OIDCAuth(OIDCAuthenticationBackend):

# override get_user method to debug token
def get_userinfo(self, access_token, id_token, payload):
"""Return user details dictionary. The id_token and payload are not used in
the default implementation, but may be used when overriding this method"""
import requests
user_response = requests.get(
self.OIDC_OP_USER_ENDPOINT,
headers={
'Authorization': 'Bearer {0}'.format(access_token)
},
verify=self.get_settings('OIDC_VERIFY_SSL', True),
timeout=self.get_settings('OIDC_TIMEOUT', None),
proxies=self.get_settings('OIDC_PROXY', None))
user_response.raise_for_status()
# LOGGER.warning(f"user info, {type(user_response.text)}, {user_response.text}")
# split on ".": Header.Payload.Signature
header, payload, signature = [self.parse_b64url(content) for content in user_response.text.split(".")]
header = json.loads(header.decode('UTF-8'))
payload = payload[:-1] if b'\x1b' in payload else payload
payload = json.loads(payload.decode('UTF-8)'))
# LOGGER.warning(f"header: {header}, \npayload: {payload}, \nsignature: {signature}")
#return user_response.json()
return payload

def parse_b64url(self, content):
"""Return decoded base64url content"""

try:
decoded = urlsafe_b64decode(content+str(b'======='))
except:
decoded = urlsafe_b64decode(content)
return decoded

def is_admin(self, groups):
if settings.OIDC_ROLES_MAP["admin"] in groups:
return True
return False

# override verify_claims to address custom OIDC_RP_SCOPES defined
def verify_claims(self, claims):
"""Verify the provided claims to decide if authentication should be allowed."""

# Verify claims required by default configuration
scopes = self.get_settings('OIDC_RP_SCOPES', 'openid email')
if 'email' in scopes.split():
return 'email' in claims

LOGGER.warning('Custom OIDC_RP_SCOPES defined. '
'You need to override `verify_claims` for custom claims verification.')

# Custom examination of OIDC_RP_SCOPES
# LOGGER.warning(f"\n DEBUG custom OIDC_RP_SCOPES (1):", OIDC_RP_SCOPES)

return True

# override get_or_create_user method
def get_or_create_user(self, access_token, id_token, payload):
"""Returns a User instance if 1 user is found. Creates a user if not found
and configured to do so. Returns nothing if multiple users are matched."""

user_info = self.get_userinfo(access_token, id_token, payload)
# LOGGER.warning("\n DEBUG user_info (1):", user_info)

claims_verified = self.verify_claims(user_info)
if not claims_verified:
msg = 'Claims verification failed'
raise SuspiciousOperation(msg)

# LOGGER.warning("\n DEBUG user_info (2):", user_info)

# email based filtering
#users = self.filter_users_by_claims(user_info)
users = User.objects.filter(username=user_info.get('preferred_username', None))

# LOGGER.warning("\n DEBUG user (3):", users)

if len(users) == 1:
return self.update_user(users[0], user_info)
elif len(users) > 1:
# In the rare case that two user accounts have the same email address,
# bail. Randomly selecting one seems really wrong.
msg = 'Multiple users returned'
raise SuspiciousOperation(msg)
elif self.get_settings('OIDC_CREATE_USER', True):
user = self.create_user(user_info)
return user
else:
LOGGER.debug('Login failed: No user with %s found, and '
'OIDC_CREATE_USER is False',
self.describe_user_by_claims(user_info))
return None

def create_user(self, claims):
data = {'email': claims[settings.OIDC_CLAIMS_MAP['email']],
'first_name': claims[settings.OIDC_CLAIMS_MAP['first_name']],
'last_name': claims[settings.OIDC_CLAIMS_MAP['last_name']],
'username': claims[settings.OIDC_CLAIMS_MAP['username']],
'is_staff': self.is_admin(claims[settings.OIDC_CLAIMS_MAP['groups']])}

# TODO: Better handling if no 'username' set. Current approach will cause duplicate record error
# TODO: Is the below sufficiently generic for different customizations for a customer?
data = {'email': claims.get(settings.OIDC_CLAIMS_MAP['email'], "[email protected]"),
'first_name': claims.get(settings.OIDC_CLAIMS_MAP['first_name'], "first_name"),
'last_name': claims.get(settings.OIDC_CLAIMS_MAP['last_name'], "last_name"),
'username': claims.get(settings.OIDC_CLAIMS_MAP['username'], "username01"),
'is_staff': False}

user = self.UserModel.objects.create_user(**data)
portfolio = Portfolio.objects.create(title=user.email.split('@')[0], description="Personal Portfolio")
portfolio.assign_owner_permissions(user)
if user.default_portfolio is None:
portfolio = user.create_default_portfolio_if_missing()
return user

def update_user(self, user, claims):
original_values = [getattr(user, x.name) for x in user._meta.get_fields() if hasattr(user, x.name)]

user.email = claims[settings.OIDC_CLAIMS_MAP['email']]
user.first_name = claims[settings.OIDC_CLAIMS_MAP['first_name']]
user.last_name = claims[settings.OIDC_CLAIMS_MAP['last_name']]
user.username = claims[settings.OIDC_CLAIMS_MAP['username']]
groups = claims[settings.OIDC_CLAIMS_MAP['groups']]
user.email = claims.get(settings.OIDC_CLAIMS_MAP['email'], "[email protected]")
user.first_name = claims.get(settings.OIDC_CLAIMS_MAP['first_name'], "missing first_name")
user.last_name = claims.get(settings.OIDC_CLAIMS_MAP['last_name'], "missing last_name")
user.username = claims.get(settings.OIDC_CLAIMS_MAP['username'], "missing username")
groups = claims.get(settings.OIDC_CLAIMS_MAP['groups'], "missing groups")
user.is_staff = self.is_admin(groups)
user.is_superuser = user.is_staff

Expand Down Expand Up @@ -97,14 +190,14 @@ def authenticate(self, request, **kwargs):
class OIDCSessionRefresh(SessionRefresh):
def process_request(self, request):
if not self.is_refreshable_url(request):
LOGGER.debug('request is not refreshable')
# LOGGER.debug('request is not refreshable')
return

expiration = request.session.get('oidc_id_token_expiration', 0)
now = time.time()
if expiration > now:
# The id_token is still valid, so we don't have to do anything.
LOGGER.debug('id token is still valid (%s > %s)', expiration, now)
# LOGGER.debug('id token is still valid (%s > %s)', expiration, now)
return

LOGGER.debug('id token has expired')
Expand Down
49 changes: 26 additions & 23 deletions siteapp/management/commands/first_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,26 +188,29 @@ def handle(self, *args, **options):
oscal_component_json = f.read()
result = ComponentImporter().import_components_as_json(import_name, oscal_component_json)

Role.objects.create(
role_id="ao", title="Authorizing Official", short_name="AO",
description="Senior federal official or executive with the authority to formally assume responsibility for operating an information system at an acceptable level of risk to organizational operations, other organizations, and the Nation."
)
Role.objects.create(
role_id="co", title="Component Owner", short_name="CO", description="Business Owner of a Component"
)
Role.objects.create(
role_id="ccp", title="Common Control Provider", short_name="CCP", description="Business owner of a Common Control"
)
Role.objects.create(
role_id="iso", title="Information System Owner", short_name="ISO", description="Business Owner of a System"
)
Role.objects.create(
role_id="isso", title="Information System Security Officer", short_name="ISSO", description="Leads effort to secure a System"
)
Role.objects.create(
role_id="isse", title="Information System Security Engineer", short_name="ISSE", description="Supports technical engineering to secure a System"
)
Role.objects.create(
role_id="poc", title="Point of Contact", short_name="PoC", description="Contact for request assistance"
)
print("GovReady-Q configuration complete.")
# Create initial roles only once
# TODO: Probably need a field to indicate if first_run has been run to avoid recreating roles that
# installation intentionally deleted.
roles_desired = [
{"role_id": "ao", "title": "Authorizing Official", "short_name": "AO", "description": "Senior federal official or executive with the authority to formally assume responsibility for operating an information system at an acceptable level of risk to organizational operations, other organizations, and the Nation."},
{"role_id":"co", "title": "Component Owner", "short_name": "CO", "description": "Business Owner of a Component"},
{"role_id": "ccp", "title": "Common Control Provider", "short_name": "CCP", "description": "Business owner of a Common Control"},
{"role_id": "iso", "title": "Information System Owner", "short_name": "ISO", "description": "Business Owner of a System"},
{"role_id": "isso", "title": "Information System Security Officer", "short_name": "ISSO", "description": "Leads effort to secure a System"},
{"role_id": "isse", "title": "Information System Security Engineer", "short_name": "ISSE", "description": "Supports technical engineering to secure a System"},
{"role_id": "poc", "title": "Point of Contact", "short_name": "PoC", "description": "Contact for request assistance"}
]
roles_to_create = []
for r in roles_desired:
if not Role.objects.filter(title=r['title']).exists():
new_role = Role(
role_id=r['role_id'],
title=r['title'],
short_name=r['short_name'],
description=r['description']
)
roles_to_create.append(new_role)
if len(roles_to_create) > 0:
roles_created = Role.objects.bulk_create(roles_to_create)

print("GovReady-Q configuration complete.")
6 changes: 3 additions & 3 deletions templates/components/element_detail_tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ <h3>Systems</h3>
{% for cs in consuming_systems %}
<div class="consuming-system">
<a class=""
href={% url 'system_element' system_id=cs.id element_id=element.id %}>
{% for p in cs.projects.all %}
href="{% url 'system_element' system_id=cs.id element_id=element.id %}">
<!-- {% for p in cs.projects.all %}
<img id="project-icon" src="{{ p.root_task.get_app_icon_url }}" alt="Project Icon" height="24">
{% endfor %}
{% endfor %} -->
{{ cs.root_element.name }}
</a>
</div>
Expand Down

0 comments on commit 05891ea

Please sign in to comment.