diff --git a/app/lib/utils.py b/app/lib/utils.py index 934ee4d..0d7d36a 100644 --- a/app/lib/utils.py +++ b/app/lib/utils.py @@ -19,7 +19,7 @@ if app.config['SAML_ENABLED']: from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser idp_timestamp = datetime(1970, 1, 1) idp_data = None - idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL']) + idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL'], entity_id=app.config.get('SAML_IDP_ENTITY_ID', None)) if idp_data is None: print('SAML: IDP Metadata initial load failed') exit(-1) @@ -37,7 +37,7 @@ def get_idp_data(): def retreive_idp_data(): global idp_data, idp_timestamp - new_idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL']) + new_idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL'], entity_id=app.config.get('SAML_IDP_ENTITY_ID', None)) if new_idp_data is not None: idp_data = new_idp_data idp_timestamp = datetime.now() @@ -205,7 +205,7 @@ def email_to_gravatar_url(email="", size=100): def prepare_flask_request(request): # If server is behind proxys or balancers use the HTTP_X_FORWARDED fields - url_data = urlparse.urlparse(request.url) + url_data = urlparse(request.url) return { 'https': 'on' if request.scheme == 'https' else 'off', 'http_host': request.host, @@ -229,7 +229,10 @@ def init_saml_auth(req): metadata = get_idp_data() settings = {} settings['sp'] = {} - settings['sp']['NameIDFormat'] = idp_data['sp']['NameIDFormat'] + if 'SAML_NAMEID_FORMAT' in app.config: + settings['sp']['NameIDFormat'] = app.config['SAML_NAMEID_FORMAT'] + 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() @@ -275,4 +278,4 @@ def init_saml_auth(req): settings['organization']['en-US']['name'] = 'PowerDNS-Admin' settings['organization']['en-US']['url'] = own_url auth = OneLogin_Saml2_Auth(req, settings) - return auth \ No newline at end of file + return auth diff --git a/app/views.py b/app/views.py index a669026..718512f 100644 --- a/app/views.py +++ b/app/views.py @@ -17,7 +17,7 @@ from flask_login import login_user, logout_user, current_user, login_required from werkzeug import secure_filename from werkzeug.security import gen_salt -from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord +from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord, Role from app import app, login_manager, github, google from app.lib import utils from app.decorators import admin_role_required, can_access_domain @@ -230,20 +230,36 @@ def saml_authorized(): self_url = self_url+req['script_name'] if 'RelayState' in request.form and self_url != request.form['RelayState']: return redirect(auth.redirect_to(request.form['RelayState'])) - user = User.query.filter_by(username=session['samlNameId'].lower()).first() + if app.config.get('SAML_ATTRIBUTE_USERNAME', False): + username = session['samlUserdata'][app.config['SAML_ATTRIBUTE_USERNAME']][0].lower() + else: + username = session['samlNameId'].lower() + user = User.query.filter_by(username=username).first() if not user: # create user - user = User(username=session['samlNameId'], + user = User(username=username, plain_text_password = None, email=session['samlNameId']) user.create_local_user() session['user_id'] = user.id - if session['samlUserdata'].has_key("email"): - user.email = session['samlUserdata']["email"][0].lower() - if session['samlUserdata'].has_key("givenname"): - user.firstname = session['samlUserdata']["givenname"][0] - if session['samlUserdata'].has_key("surname"): - user.lastname = session['samlUserdata']["surname"][0] + logging.debug("Attributes are: {0}".format(repr(session['samlUserdata']))) + 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') + admin_attribute_name = app.config.get('SAML_ATTRIBUTE_ADMIN', None) + 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 'true' in session['samlUserdata'].get(admin_attribute_name, []): + logging.debug("User is an admin") + user.role_id = Role.query.filter_by(name='Administrator').first().id + else: + logging.debug("User is NOT an admin") + user.role_id = Role.query.filter_by(name='User').first().id user.plain_text_password = None user.update_profile() session['external_auth'] = True diff --git a/config_template.py b/config_template.py index cbc4d2a..8d615d0 100644 --- a/config_template.py +++ b/config_template.py @@ -97,6 +97,46 @@ SAML_PATH = os.path.join(os.path.dirname(__file__), 'saml') SAML_METADATA_URL = 'https:///FederationMetadata/2007-06/FederationMetadata.xml' #Cache Lifetime in Seconds SAML_METADATA_CACHE_LIFETIME = 1 + +## EntityID of the IdP to use. Only needed if more than one IdP is +## in the SAML_METADATA_URL +### Default: First (only) IdP in the SAML_METADATA_URL +### Example: https://idp.example.edu/idp +#SAML_IDP_ENTITY_ID = 'https://idp.example.edu/idp' +## NameID format to request +### Default: The SAML NameID Format in the metadata if present, +### otherwise urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified +### Example: urn:oid:0.9.2342.19200300.100.1.1 +#SAML_NAMEID_FORMAT = 'urn:oid:0.9.2342.19200300.100.1.1' + +## Attribute to use for Email address +### Default: email +### Example: urn:oid:0.9.2342.19200300.100.1.3 +#SAML_ATTRIBUTE_EMAIL = 'urn:oid:0.9.2342.19200300.100.1.3' + +## Attribute to use for Given name +### Default: givenname +### Example: urn:oid:2.5.4.42 +#SAML_ATTRIBUTE_GIVENNAME = 'urn:oid:2.5.4.42' + +## Attribute to use for Surname +### Default: surname +### Example: urn:oid:2.5.4.4 +#SAML_ATTRIBUTE_SURNAME = 'urn:oid:2.5.4.4' + +## Attribute to use for username +### Default: Use NameID instead +### Example: urn:oid:0.9.2342.19200300.100.1.1 +#SAML_ATTRIBUTE_USERNAME = 'urn:oid:0.9.2342.19200300.100.1.1' + +## Attribute to get admin status from +### Default: Don't control admin with SAML attribute +### Example: https://example.edu/pdns-admin +### If set, look for the value 'true' to set a user as an administrator +### If not included in assertion, or set to something other than 'true', +### the user is set as a non-administrator user. +#SAML_ATTRIBUTE_ADMIN = 'https://example.edu/pdns-admin' + SAML_SP_ENTITY_ID = 'http://' SAML_SP_CONTACT_NAME = '' SAML_SP_CONTACT_MAIL = ''