mirror of
https://github.com/cwinfo/powerdns-admin.git
synced 2025-06-14 12:06:06 +00:00
Improve SAML support
- Make SAML_WANT_MESSAGE_SIGNED configurable, AzureAD signs the assertion but wouldn't sign the message - Add support for a name attribute, i.e. 'Tim Jacomb' using `SAML_ATTRIBUTE_NAME`, which will be mapped into the given and surname fields, AzureAD only has displayname - Add support for group based admin `SAML_ATTRIBUTE_GROUP` and `SAML_GROUP_ADMIN_NAME` - Add support for group based accounts `SAML_GROUP_TO_ACCOUNT_MAPPING` - Don't fail if cert and key aren't present
This commit is contained in:
@ -3,6 +3,7 @@ import json
|
||||
import requests
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import os
|
||||
|
||||
from app import app
|
||||
from distutils.version import StrictVersion
|
||||
@ -244,10 +245,12 @@ def init_saml_auth(req):
|
||||
else:
|
||||
settings['sp']['NameIDFormat'] = idp_data.get('sp', {}).get('NameIDFormat', 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified')
|
||||
settings['sp']['entityId'] = app.config['SAML_SP_ENTITY_ID']
|
||||
cert = open(CERT_FILE, "r").readlines()
|
||||
key = open(KEY_FILE, "r").readlines()
|
||||
settings['sp']['privateKey'] = "".join(key)
|
||||
settings['sp']['x509cert'] = "".join(cert)
|
||||
if os.path.isfile(CERT_FILE):
|
||||
cert = open(CERT_FILE, "r").readlines()
|
||||
settings['sp']['x509cert'] = "".join(cert)
|
||||
if os.path.isfile(KEY_FILE):
|
||||
key = open(KEY_FILE, "r").readlines()
|
||||
settings['sp']['privateKey'] = "".join(key)
|
||||
settings['sp']['assertionConsumerService'] = {}
|
||||
settings['sp']['assertionConsumerService']['binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
||||
settings['sp']['assertionConsumerService']['url'] = own_url+'/saml/authorized'
|
||||
@ -273,7 +276,7 @@ def init_saml_auth(req):
|
||||
settings['security']['nameIdEncrypted'] = False
|
||||
settings['security']['signMetadata'] = True
|
||||
settings['security']['wantAssertionsSigned'] = True
|
||||
settings['security']['wantMessagesSigned'] = True
|
||||
settings['security']['wantMessagesSigned'] = app.config.get('SAML_WANT_MESSAGE_SIGNED', True)
|
||||
settings['security']['wantNameIdEncrypted'] = False
|
||||
settings['contactPerson'] = {}
|
||||
settings['contactPerson']['support'] = {}
|
||||
|
80
app/views.py
80
app/views.py
@ -293,27 +293,42 @@ def saml_authorized():
|
||||
email_attribute_name = app.config.get('SAML_ATTRIBUTE_EMAIL', 'email')
|
||||
givenname_attribute_name = app.config.get('SAML_ATTRIBUTE_GIVENNAME', 'givenname')
|
||||
surname_attribute_name = app.config.get('SAML_ATTRIBUTE_SURNAME', 'surname')
|
||||
name_attribute_name = app.config.get('SAML_ATTRIBUTE_NAME', None)
|
||||
account_attribute_name = app.config.get('SAML_ATTRIBUTE_ACCOUNT', None)
|
||||
admin_attribute_name = app.config.get('SAML_ATTRIBUTE_ADMIN', None)
|
||||
group_attribute_name = app.config.get('SAML_ATTRIBUTE_GROUP', None)
|
||||
admin_group_name = app.config.get('SAML_GROUP_ADMIN_NAME', None)
|
||||
group_to_account_mapping = create_group_to_account_mapping()
|
||||
|
||||
if email_attribute_name in session['samlUserdata']:
|
||||
user.email = session['samlUserdata'][email_attribute_name][0].lower()
|
||||
if givenname_attribute_name in session['samlUserdata']:
|
||||
user.firstname = session['samlUserdata'][givenname_attribute_name][0]
|
||||
if surname_attribute_name in session['samlUserdata']:
|
||||
user.lastname = session['samlUserdata'][surname_attribute_name][0]
|
||||
if admin_attribute_name:
|
||||
if name_attribute_name in session['samlUserdata']:
|
||||
name = session['samlUserdata'][name_attribute_name][0].split(' ')
|
||||
user.firstname = name[0]
|
||||
user.lastname = ' '.join(name[1:])
|
||||
|
||||
if group_attribute_name:
|
||||
user_groups = session['samlUserdata'].get(group_attribute_name, [])
|
||||
else:
|
||||
user_groups = []
|
||||
if admin_attribute_name or group_attribute_name:
|
||||
user_accounts = set(user.get_account())
|
||||
saml_accounts = []
|
||||
for group_mapping in group_to_account_mapping:
|
||||
mapping = group_mapping.split('=')
|
||||
group = mapping[0]
|
||||
account_name = mapping[1]
|
||||
|
||||
if group in user_groups:
|
||||
account = handle_account(account_name)
|
||||
saml_accounts.append(account)
|
||||
|
||||
for account_name in session['samlUserdata'].get(account_attribute_name, []):
|
||||
clean_name = ''.join(c for c in account_name.lower() if c in "abcdefghijklmnopqrstuvwxyz0123456789")
|
||||
if len(clean_name) > Account.name.type.length:
|
||||
logging.error("Account name {0} too long. Truncated.".format(clean_name))
|
||||
account = Account.query.filter_by(name=clean_name).first()
|
||||
if not account:
|
||||
account = Account(name=clean_name.lower(), description='', contact='', mail='')
|
||||
account.create_account()
|
||||
history = History(msg='Account {0} created'.format(account.name), created_by='SAML Assertion')
|
||||
history.add()
|
||||
account = handle_account(account_name)
|
||||
saml_accounts.append(account)
|
||||
saml_accounts = set(saml_accounts)
|
||||
for account in saml_accounts - user_accounts:
|
||||
@ -324,14 +339,11 @@ def saml_authorized():
|
||||
account.remove_user(user)
|
||||
history = History(msg='Removing {0} from account {1}'.format(user.username, account.name), created_by='SAML Assertion')
|
||||
history.add()
|
||||
if admin_attribute_name:
|
||||
if 'true' in session['samlUserdata'].get(admin_attribute_name, []):
|
||||
admin_role = Role.query.filter_by(name='Administrator').first().id
|
||||
if user.role_id != admin_role:
|
||||
user.role_id = admin_role
|
||||
history = History(msg='Promoting {0} to administrator'.format(user.username), created_by='SAML Assertion')
|
||||
history.add()
|
||||
else:
|
||||
if admin_attribute_name and 'true' in session['samlUserdata'].get(admin_attribute_name, []):
|
||||
uplift_to_admin(user)
|
||||
elif admin_group_name in user_groups:
|
||||
uplift_to_admin(user)
|
||||
elif admin_attribute_name or group_attribute_name:
|
||||
user_role = Role.query.filter_by(name='User').first().id
|
||||
if user.role_id != user_role:
|
||||
user.role_id = user_role
|
||||
@ -343,7 +355,37 @@ def saml_authorized():
|
||||
login_user(user, remember=False)
|
||||
return redirect(url_for('index'))
|
||||
else:
|
||||
return render_template('errors/SAML.html', errors=errors)
|
||||
return render_template('errors/SAML.html', errors=errors)
|
||||
|
||||
|
||||
def create_group_to_account_mapping():
|
||||
group_to_account_mapping_string = app.config.get('SAML_GROUP_TO_ACCOUNT_MAPPING', None)
|
||||
if group_to_account_mapping_string and len(group_to_account_mapping_string.strip()) > 0:
|
||||
group_to_account_mapping = group_to_account_mapping_string.split(',')
|
||||
else:
|
||||
group_to_account_mapping = []
|
||||
return group_to_account_mapping
|
||||
|
||||
|
||||
def handle_account(account_name):
|
||||
clean_name = ''.join(c for c in account_name.lower() if c in "abcdefghijklmnopqrstuvwxyz0123456789")
|
||||
if len(clean_name) > Account.name.type.length:
|
||||
logging.error("Account name {0} too long. Truncated.".format(clean_name))
|
||||
account = Account.query.filter_by(name=clean_name).first()
|
||||
if not account:
|
||||
account = Account(name=clean_name.lower(), description='', contact='', mail='')
|
||||
account.create_account()
|
||||
history = History(msg='Account {0} created'.format(account.name), created_by='SAML Assertion')
|
||||
history.add()
|
||||
return account
|
||||
|
||||
|
||||
def uplift_to_admin(user):
|
||||
admin_role = Role.query.filter_by(name='Administrator').first().id
|
||||
if user.role_id != admin_role:
|
||||
user.role_id = admin_role
|
||||
history = History(msg='Promoting {0} to administrator'.format(user.username), created_by='SAML Assertion')
|
||||
history.add()
|
||||
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
|
Reference in New Issue
Block a user