Added UI Interface for SAML Settings

This commit is contained in:
vmarkop 2021-12-15 16:44:13 +02:00
parent 3d839c92d2
commit 5d533173b1
4 changed files with 50 additions and 234 deletions

View file

@ -110,15 +110,15 @@ class Setting(db.Model):
'oidc_oauth_email': 'email',
'oidc_oauth_account_name_property': '',
'oidc_oauth_account_description_property': '',
'saml_enabled': True,
'saml_enabled': False,
'saml_debug': True,
'saml_metadata_url': 'https://md.aai.grnet.gr/aggregates/grnet-metadata.xml',
'saml_metadata_url': 'https://example.com/metadata.xml',
'saml_metadata_cache_lifetime': '15',
'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
'saml_idp_slo_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
'saml_sp_acs_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
'saml_sp_sls_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
'saml_idp_entity_id': 'https://idp.uoa.gr/idp/shibboleth',
'saml_idp_entity_id': 'https://idp.example.com/idp',
'saml_nameid_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
'saml_sp_requested_attributes': '[ \
{"name": "urn:oid:0.9.2342.19200300.100.1.1", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": true, "friendlyName": "username" }, \
@ -136,29 +136,25 @@ class Setting(db.Model):
'saml_attribute_group': None,
'saml_group_admin_name': None,
'saml_group_to_account_mapping': None,
'saml_sp_entity_id': 'https://dns.uoa.gr',
'saml_sp_entity_id': 'https://pdnsa.example.com',
'saml_sp_contact_name': 'admin',
'saml_sp_contact_mail': 'powerdnsadmin@organization.com',
'saml_cert_file': '/etc/pki/powerdns-admin/cert.crt',
'saml_cert_key': '/etc/pki/powerdns-admin/key.pem',
'saml_sign_authn_request': False,
'saml_sign_logout_request_response': False,
'saml_logout': True,
'saml_logout': False,
'saml_logout_url': 'https://google.com',
'saml_want_assertions_encrypted': False,
'saml_digest_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
'saml_signature_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
'saml_want_assertions_signed': True,
'saml_want_assertions_signed': False,
'saml_sign_metadata': False,
'saml_want_message_signed': False,
'saml_nameid_encrypted': False,
'saml_want_nameid_encrypted': False,
'saml_metadata_cache_duration': 'PT5M',
'saml_metadata_valid_until': '2021-12-31T00:00:00Z',
'saml_autoprovisioning': True,
'saml_urn_prefix': 'urn:mace:uoa.gr',
'saml_autoprovisioning_attribute': 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7',
'saml_purge': False,
'saml_metadata_cache_duration': None,
'saml_metadata_valid_until': None,
'forward_records_allow_edit': {
'A': True,
'AAAA': True,

View file

@ -22,7 +22,6 @@ from ..models.domain_template import DomainTemplate
from ..models.domain_template_record import DomainTemplateRecord
from ..models.api_key import ApiKey
from ..models.base import db
from ..services.saml import SAML
from ..lib.schema import ApiPlainKeySchema
apikey_plain_schema = ApiPlainKeySchema(many=True)
@ -1729,29 +1728,6 @@ def setting_authentication():
Setting().set('saml_metadata_valid_until',
request.form.get('saml_metadata_valid_until'))
Setting().set(
'saml_autoprovisioning', True
if request.form.get('saml_autoprovisioning') == 'ON' else False)
if request.form.get('autoprovisioning')=='ON':
Setting().set('saml_autoprovisioning_attribute',
request.form.get('saml_autoprovisioning_attribute'))
if validateURN(request.form.get('saml_urn_value')):
Setting().set('saml_urn_value',
request.form.get('saml_urn_value'))
else:
return render_template('admin_setting_authentication.html',
error="Invalid urn")
else:
if request.form.get('saml_autoprovisioning_attribute'):
Setting().set('saml_autoprovisioning_attribute',
request.form.get('saml_autoprovisioning_attribute'))
if request.form.get('saml_urn_value'):
Setting().set('saml_urn_value',
request.form.get('saml_urn_value'))
Setting().set('saml_purge', True
if request.form.get('purge') == 'ON' else False)
result = {
'status': True,
'msg':

View file

@ -1030,80 +1030,51 @@ def saml_authorized():
user.firstname = name[0]
user.lastname = ' '.join(name[1:])
if not Setting().get('saml_autoprovisioning'):
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_accounts())
saml_accounts = []
for group_mapping in group_to_account_mapping:
mapping = group_mapping.split('=')
group = mapping[0]
account_name = mapping[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_accounts())
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, []):
if group in user_groups:
account = handle_account(account_name)
saml_accounts.append(account)
saml_accounts = set(saml_accounts)
for account in saml_accounts - user_accounts:
account.add_user(user)
history = History(msg='Adding {0} to account {1}'.format(
user.username, account.name),
created_by='SAML Assertion')
history.add()
for account in user_accounts - saml_accounts:
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 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:
if user.role.name != 'User':
user.role_id = Role.query.filter_by(name='User').first().id
history = History(msg='Demoting {0} to user'.format(
user.username),
created_by='SAML Assertion')
history.add()
elif Setting().get('saml_autoprovisioning'):
urn_prefix = Setting().get('saml_urn_prefix')
autoprovisioning_attribute = Setting().get('saml_autoprovisioning_attribute')
Entitlements = []
if autoprovisioning_attribute in session['samlUserdata']:
for k in session['samlUserdata'][autoprovisioning_attribute]:
Entitlements.append(k)
if len(Entitlements)==0 and Setting().get('saml_purge'):
if user.role.name != 'User':
user.role_id = Role.query.filter_by(name='User').first().id
history = History(msg='Demoting {0} to user'.format(
user.username),
created_by='SAML Autoprovision')
history.add()
elif len(Entitlements)!=0:
if checkForPDAEntries(Entitlements, urn_prefix):
user.updateUser(Entitlements, urn_prefix)
else:
current_app.logger.warning('Not a single powerdns-admin record was found, possibly a typo in the prefix')
if Setting().get('saml_purge'):
current_app.logger.warning('Procceding to revoke every privilige from ' + user.username + '.' )
if user.role.name != 'User':
user.role_id = Role.query.filter_by(name='User').first().id
history = History(msg='Demoting {0} to user'.format(
user.username),
created_by='SAML Autoprovision')
history.add()
for account_name in session['samlUserdata'].get(
account_attribute_name, []):
account = handle_account(account_name)
saml_accounts.append(account)
saml_accounts = set(saml_accounts)
for account in saml_accounts - user_accounts:
account.add_user(user)
history = History(msg='Adding {0} to account {1}'.format(
user.username, account.name),
created_by='SAML Assertion')
history.add()
for account in user_accounts - saml_accounts:
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 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:
if user.role.name != 'User':
user.role_id = Role.query.filter_by(name='User').first().id
history = History(msg='Demoting {0} to user'.format(
user.username),
created_by='SAML Assertion')
history.add()
user.plain_text_password = None
user.update_profile()

View file

@ -869,38 +869,6 @@
<input type="text" class="form-control" name="saml_attribute_account" id="saml_attribute_account" placeholder="e.g. https://example.edu/pdns-account" data-error="Please input SAML Account Attribute" value="{{ SETTING.get('saml_attribute_account') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group">
<label>Roles Autoprovisioning</label> <br />
<label>
<input type="radio" name="saml_autoprovisioning" id="saml_autoprovisioning_off" value="OFF"
{% if not SETTING.get('saml_autoprovisioning') %}checked{% endif %}> OFF
</label>
&nbsp;&nbsp;&nbsp;
<label>
<input type="radio" name="saml_autoprovisioning" id="saml_autoprovisioning_on" value="ON"
{% if SETTING.get('saml_autoprovisioning') %}checked{% endif %}> ON
</div>
<div class="form-group">
<label for="saml_autoprovisioning_attribute">Roles provisioning field</label>
<input type="text" class="form-control" name="saml_autoprovisioning_attribute" id="saml_autoprovisioning_attribute" placeholder="e.g. eduPersonEntitlement" data-error="Please input field responsible for autoprovisioning" value="{{ SETTING.get('saml_autoprovisioning_attribute') }}">
<span class="help-block with-errors"></span>
</div>
<div class="form-group {% if error %}has-error{% endif %}">
<label for="saml_urn_prefix">Urn prefix</label>
<input type="text" class="form-control" name="saml_urn_prefix" id="saml_urn_prefix" placeholder="e.g. urn:mace:<yourOrganization>" data-error="Please fill this field" value="{{ SETTING.get('saml_urn_prefix') }}">
{% if error %}
<span class="help-block with-errors">Please input the correct prefix for your urn value</span>
{% endif %}
</div>
<div class="form-group">
<label>Purge Roles If Empty</label> <br />
<label>
<input type="radio" name="saml_purge" id="saml_purge_off" value="OFF" {% if not SETTING.get('saml_purge') %}checked{% endif %}> OFF
</label>
&nbsp;&nbsp;&nbsp;
<label>
<input type="radio" name="saml_purge" id="saml_purge_on" value="ON" {% if SETTING.get('saml_purge') %}checked{% endif %}> ON
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Save</button>
@ -1139,43 +1107,6 @@
be created and the user added to them.
</li>
</ul>
Provision PDA Role/Domains/Accounts based on urn SAML Attributes.<br>
<ul>
<li>
<b>Roles Autoprovisioning</b> - If toggled on, the PDA Role and the associations of users found in the local db, will be instantly updated from the SAML SP db every time they log in.<br>
<b style="color:red;">NOTE</b>:This feature and the assertion of "Admin / Account" attributes are mutually exclusive.<br>
If used, the values for Admin/Account given above will be ignored.
</li>
<li>
<b>Roles provisioning field</b> - The urn value of the attribute in the SAML Token where PDA will look for a new Role and/or new associations to domains/accounts.<br>
e.g. <i>urn:oid:x.x.x.x.x</i><br>
The allowed syntax for records inside this attribute in your SAML Token is: <br>
<ul>if <i>PDA-Role∈[Administrator, Operator]</i>:
<ul>
<li>
prefix:"powerdns-admin":PDA-Role
</li>
</ul>
else (<i>if user</i>): <br>
<ul>
<li>
prefix:"powerdns-admin":PDA-Role:&ltdomain&gt:&ltaccount&gt
</li>
</ul>
where <i>prefix</i> is given in the field "Urn prefix".
</ul>
</li>
<li>
<b>Urn prefix</b> - The prefix used before the static keyword "powerdns-admin" for your entitlements in the SAML token.
Must comply with RFC no.8141.<br> e.g.<i>urn:mace:&ltyour_organization&gt</i>
</li>
<li>
<b>Purge Roles If Empty</b> - If toggled on, SAML logins that have no valid "powerdns-admin" records to their autoprovisioning field,
will lose all their associations with any domain or account, also reverting to a User in the process, despite their current role in the local db.<br>
If toggled off, in the same scenario they get to keep their existing associations and their current Role.<br>
<b style="color:red;">CAUTION</b>: Enabling this feature will revoke existing users' access to their associated domains unless they have their autoprovisioning field prepopulated.
</li>
</ul>
</dd>
</dl>
</div>
@ -1562,23 +1493,6 @@
//END: OIDC Tab JS
// START: SAML tab js
$('#saml_autoprovisioning_on').iCheck({
radioClass: 'iradio_square-blue',
increaseArea: '20%'
});
$('#saml_autoprovisioning_off').iCheck({
radioClass: 'iradio_square-blue',
increaseArea: '20%'
});
$('#saml_purge_on').iCheck({
radioClass: 'iradio_square-blue',
increaseArea: '20%'
});
$('#saml_purge_off').iCheck({
radioClass: 'iradio_square-blue',
increaseArea: '20%'
});
$('#saml_enabled').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'
@ -1601,10 +1515,6 @@
if ($('#saml_logout').is(":checked")) {
$('#saml_logout_url').prop('required', true);
}
if ($('#saml_autoprovisioning_on').is(":checked")) {
$('#saml_autoprovisioning_attribute').prop('required', true);
$('#saml_urn_prefix').prop('required', true);
}
} else {
$('#saml_metadata_url').prop('required', false);
$('#saml_idp_sso_binding').prop('required', false);
@ -1620,8 +1530,6 @@
$('#saml_digest_algorithm').prop('required', false);
$('#saml_signature_algorithm').prop('required', false);
$('#saml_logout_url').prop('required', false);
$('#saml_autoprovisioning_attribute').prop('required', false);
$('#saml_urn_prefix').prop('required', false);
}
});
// init validation requirement at first time page load
@ -1642,10 +1550,6 @@
if ($('#saml_logout').is(":checked")) {
$('#saml_logout_url').prop('required', true);
}
if ($('#saml_autoprovisioning_on').is(":checked")) {
$('#saml_autoprovisioning_attribute').prop('required', true);
$('#saml_urn_prefix').prop('required', true);
}
{% endif %}
$(document.body).on("focus", "#saml_sp_requested_attributes", function (e) {
@ -1664,37 +1568,6 @@
modal.modal('show');
});
$("#saml_autoprovisioning_on" ).on('ifChanged',function(){
if ($('#saml_autoprovisioning_on').is(":checked") && $('#saml_enabled').is(":checked")) {
$('#saml_autoprovisioning_attribute').prop('required', true);
$('#saml_urn_prefix').prop('required', true);
}
else{
$('#saml_autoprovisioning_attribute').prop('required', false);
$('#saml_urn_prefix').prop('required', false);
}
});
$('#saml_purge_on').on('ifClicked', function(event){
if( !$(event.target).is(':checked')){
var modal = $("#modal_confirm");
var info = "Are you sure you want to do this? Users will lose their associated domains unless they already have their autoprovisioning field prepopulated." ;
modal.find('.modal-body p').text(info);
modal.find('#button_confirm').click(function () {
modal.modal('hide');
})
modal.find('#button_cancel').click(function () {
$('#saml_purge_off').iCheck('check');
modal.modal('hide');
})
modal.find('#X').click(function () {
$('#saml_purge_off').iCheck('check');
modal.modal('hide');
})
modal.modal('show');
}
});
$('#saml_logout').iCheck({
checkboxClass : 'icheckbox_square-blue',
increaseArea : '20%'