mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
External IdP: add Web UI to manage IdP references
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> Reviewed-By: Francisco Trivino <ftrivino@redhat.com> Reviewed-By: Sumit Bose <sbose@redhat.com> Reviewed-By: Francisco Trivino <ftrivino@redhat.com> Reviewed-By: Sumit Bose <sbose@redhat.com>
This commit is contained in:
@@ -58,6 +58,7 @@ define([
|
||||
'./topology',
|
||||
'./user',
|
||||
'./vault',
|
||||
'./idp',
|
||||
'dojo/domReady!'
|
||||
],function(app_container) {
|
||||
return app_container;
|
||||
|
||||
@@ -136,6 +136,10 @@ return {
|
||||
{
|
||||
label: '@i18n:authtype.type_hardened',
|
||||
value: 'hardened'
|
||||
},
|
||||
{
|
||||
label: '@i18n:authtype.type_idp',
|
||||
value: 'idp'
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
|
||||
296
install/ui/src/freeipa/idp.js
Normal file
296
install/ui/src/freeipa/idp.js
Normal file
@@ -0,0 +1,296 @@
|
||||
// Copyright (C) 2022 FreeIPA Contributors see COPYING for license
|
||||
|
||||
define([
|
||||
'dojo/on',
|
||||
'./ipa',
|
||||
'./jquery',
|
||||
'./menu',
|
||||
'./phases',
|
||||
'./reg',
|
||||
'./text',
|
||||
'./details',
|
||||
'./search',
|
||||
'./entity',
|
||||
'./dialogs/password'
|
||||
],
|
||||
function(on, IPA, $, menu, phases, reg, text) {
|
||||
|
||||
/**
|
||||
* IdP module
|
||||
* @class
|
||||
* @singleton
|
||||
*/
|
||||
var idp = IPA.idp = {};
|
||||
|
||||
// Templates are hardcoded in idp_add class
|
||||
// and cannot be retrieved with an API call
|
||||
// Structure below references template names
|
||||
// and additional fields per template
|
||||
idp.templates = [
|
||||
{ value: 'keycloak',
|
||||
label: text.get('@i18n:objects.idp.template_keycloak'),
|
||||
fields: ['ipaidporg', 'ipaidpbaseurl']},
|
||||
{ value: 'google',
|
||||
label: text.get('@i18n:objects.idp.template_google'),
|
||||
fields: []},
|
||||
{ value: 'github',
|
||||
label: text.get('@i18n:objects.idp.template_github'),
|
||||
fields: []},
|
||||
{ value: 'microsoft',
|
||||
label: text.get('@i18n:objects.idp.template_microsoft'),
|
||||
fields: ['ipaidporg']},
|
||||
{ value: 'okta',
|
||||
label: text.get('@i18n:objects.idp.template_okta'),
|
||||
fields: ['ipaidporg', 'ipaidpbaseurl']}
|
||||
];
|
||||
|
||||
|
||||
var make_spec = function() {
|
||||
return {
|
||||
name: 'idp',
|
||||
enable_test: function() {
|
||||
return true;
|
||||
},
|
||||
facets: [
|
||||
{
|
||||
$type: 'search',
|
||||
columns: [
|
||||
'cn',
|
||||
'ipaidpclientid',
|
||||
'ipaidpscope',
|
||||
'description'
|
||||
]
|
||||
},
|
||||
{
|
||||
$type: 'details',
|
||||
sections: [
|
||||
{
|
||||
name: 'idpclient',
|
||||
label: '@i18n:objects.idp.label_idpclient',
|
||||
fields: [
|
||||
'cn',
|
||||
'ipaidpclientid',
|
||||
{
|
||||
$type: 'password',
|
||||
name: 'ipaidpclientsecret',
|
||||
flags: ['w_if_no_aci']
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'idp',
|
||||
label: '@i18n:objects.idp.label_idp',
|
||||
fields: [
|
||||
'ipaidpscope',
|
||||
'ipaidpsub',
|
||||
'ipaidpauthendpoint',
|
||||
'ipaidpdevauthendpoint',
|
||||
'ipaidptokenendpoint',
|
||||
'ipaidpuserinfoendpoint',
|
||||
'ipaidpkeysendpoint',
|
||||
'ipaidpissuerurl'
|
||||
]
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
$type: 'password',
|
||||
dialog: {
|
||||
password_name: 'ipaidpclientsecret'
|
||||
}
|
||||
}
|
||||
],
|
||||
header_actions: ['password']
|
||||
}
|
||||
],
|
||||
adder_dialog: {
|
||||
title: '@i18n:objects.idp.add',
|
||||
policies: [
|
||||
IPA.add_idp_policy
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
name: 'idpclientsetup',
|
||||
label: '@i18n:objects.idp.label_idpclient',
|
||||
fields: [
|
||||
'cn',
|
||||
'ipaidpclientid',
|
||||
{
|
||||
$type: 'password',
|
||||
name: 'ipaidpclientsecret'
|
||||
},
|
||||
{
|
||||
$type: 'password',
|
||||
name: 'ipaidpclientsecret_verify',
|
||||
label: '@i18n:objects.idp.verify_secret',
|
||||
flags: ['no_command'],
|
||||
required: false,
|
||||
validators: [{
|
||||
$type: 'same_password',
|
||||
other_field: 'ipaidpclientsecret'
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'idpsetup',
|
||||
label: '@i18n:objects.idp.label_idp',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
label: 'Provider type',
|
||||
$type: 'radio',
|
||||
flags: ['no_command'],
|
||||
layout: 'vertical',
|
||||
default_value: 'template',
|
||||
options: [
|
||||
{
|
||||
value: 'template',
|
||||
label: 'Pre-defined IdP template',
|
||||
},
|
||||
{
|
||||
value: 'custom',
|
||||
label: 'Custom IdP definition',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '@i18n:idp.provider',
|
||||
name: 'ipaidpprovider',
|
||||
$type: 'select',
|
||||
options: IPA.create_options(idp.templates)
|
||||
},
|
||||
{
|
||||
name: 'ipaidporg',
|
||||
label: '@i18n:objects.idp.ipaidporg',
|
||||
metadata: '@mc-opt:idp_add:ipaidporg'
|
||||
},
|
||||
{
|
||||
name: 'ipaidpbaseurl',
|
||||
label: '@i18n:objects.idp.ipaidpbaseurl',
|
||||
metadata: '@mc-opt:idp_add:ipaidpbaseurl'
|
||||
},
|
||||
'ipaidpscope',
|
||||
'ipaidpsub',
|
||||
'ipaidpauthendpoint',
|
||||
'ipaidpdevauthendpoint',
|
||||
'ipaidptokenendpoint',
|
||||
'ipaidpuserinfoendpoint',
|
||||
'ipaidpkeysendpoint'
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
deleter_dialog: {
|
||||
title: '@i18n:objects.idp.remove'
|
||||
}
|
||||
};};
|
||||
|
||||
IPA.add_idp_policy = function() {
|
||||
// Custom provider fields
|
||||
// need to be hidden for a chosen template
|
||||
// and show for the custom provider
|
||||
var custom_fields = [
|
||||
'ipaidpauthendpoint',
|
||||
'ipaidpdevauthendpoint',
|
||||
'ipaidptokenendpoint',
|
||||
'ipaidpuserinfoendpoint',
|
||||
'ipaidpkeysendpoint'
|
||||
];
|
||||
|
||||
// Template may require an additional field
|
||||
// Make it required and visible in that case
|
||||
var template_fields = [
|
||||
'ipaidporg',
|
||||
'ipaidpbaseurl'
|
||||
];
|
||||
|
||||
var that = IPA.facet_policy();
|
||||
|
||||
that.init = function() {
|
||||
// Handle choice of either pre-defined or custom IdP
|
||||
var type_f = that.container.fields.get_field('type');
|
||||
on(type_f, 'value-change', that.on_type_change);
|
||||
|
||||
// Handle choice of a pre-defined IdP to show/remove additional fields
|
||||
var prov_f = that.container.fields.get_field('ipaidpprovider');
|
||||
on(prov_f, 'value-change', that.on_prov_change);
|
||||
};
|
||||
|
||||
that.on_type_change = function() {
|
||||
var type_f = that.container.fields.get_field('type');
|
||||
var mode = type_f.get_value()[0];
|
||||
var show_custom = true;
|
||||
|
||||
if (mode === 'template') show_custom = false;
|
||||
|
||||
// For custom template we show custom fields
|
||||
// and mark all of them required and passed to the RPC
|
||||
// If show_custom is false, the opposite happens
|
||||
custom_fields.forEach(fname => {
|
||||
widget_f = that.container.fields.get_field(fname);
|
||||
widget_f.set_required(show_custom);
|
||||
widget_f.set_enabled(show_custom);
|
||||
widget_f.widget.set_visible(show_custom);
|
||||
});
|
||||
|
||||
// For template fields we show them if custom aren't shown
|
||||
template_fields.forEach(fname => {
|
||||
widget_f = that.container.fields.get_field(fname);
|
||||
widget_f.set_enabled(!show_custom);
|
||||
widget_f.widget.set_visible(!show_custom);
|
||||
});
|
||||
|
||||
widget_f = that.container.fields.get_field('ipaidpprovider');
|
||||
widget_f.set_required(!show_custom);
|
||||
widget_f.set_enabled(!show_custom);
|
||||
widget_f.widget.set_visible(!show_custom);
|
||||
};
|
||||
|
||||
that.on_prov_change = function() {
|
||||
var prov_f = that.container.fields.get_field('ipaidpprovider');
|
||||
var value = prov_f.get_value()[0];
|
||||
|
||||
// First, clear template fields from the previous provider choice
|
||||
template_fields.forEach(fname => {
|
||||
widget_f = that.container.fields.get_field(fname);
|
||||
widget_f.widget.set_visible(false);
|
||||
widget_f.set_required(false);
|
||||
widget_f.set_enabled(false);
|
||||
});
|
||||
|
||||
// Second, enable and get required template-specific fields
|
||||
idp.templates.forEach(idp_v => {
|
||||
if (idp_v['value'] == value) {
|
||||
idp_v['fields'].forEach(fname => {
|
||||
widget_f = that.container.fields.get_field(fname);
|
||||
widget_f.set_required(true);
|
||||
widget_f.set_enabled(true);
|
||||
widget_f.widget.set_visible(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
};
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
/**
|
||||
* IdP specification object
|
||||
*/
|
||||
idp.spec = make_spec();
|
||||
|
||||
/**
|
||||
* Register IdP entity
|
||||
*/
|
||||
idp.register = function() {
|
||||
var e = reg.entity;
|
||||
e.register({type: 'idp', spec: idp.spec});
|
||||
};
|
||||
|
||||
phases.on('registration', idp.register);
|
||||
|
||||
return idp;
|
||||
});
|
||||
@@ -185,6 +185,7 @@ var nav = {};
|
||||
},
|
||||
{ entity: 'otptoken' },
|
||||
{ entity: 'radiusproxy' },
|
||||
{ entity: 'idp' },
|
||||
{
|
||||
entity: 'certmaprule',
|
||||
facet: 'search',
|
||||
|
||||
@@ -166,7 +166,18 @@ return {
|
||||
name: 'krbauthindmaxticketlife_hardened',
|
||||
acl_param: 'krbauthindmaxticketlife',
|
||||
measurement_unit: 'seconds'
|
||||
},
|
||||
{
|
||||
name: 'krbauthindmaxrenewableage_idp',
|
||||
acl_param: 'krbauthindmaxrenewableage',
|
||||
measurement_unit: 'seconds'
|
||||
},
|
||||
{
|
||||
name: 'krbauthindmaxticketlife_idp',
|
||||
acl_param: 'krbauthindmaxticketlife',
|
||||
measurement_unit: 'seconds'
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -112,7 +112,8 @@ return {
|
||||
{ label: '@i18n:authtype.type_radius', value: 'radius' },
|
||||
{ label: '@i18n:authtype.type_otp', value: 'otp' },
|
||||
{ label: '@i18n:authtype.type_pkinit', value: 'pkinit' },
|
||||
{ label: '@i18n:authtype.type_hardened', value: 'hardened' }
|
||||
{ label: '@i18n:authtype.type_hardened', value: 'hardened' },
|
||||
{ label: '@i18n:authtype.type_idp', value: 'idp' }
|
||||
],
|
||||
tooltip: {
|
||||
title: '@i18n:authtype.config_tooltip',
|
||||
|
||||
@@ -146,6 +146,10 @@ return {
|
||||
{
|
||||
label: '@i18n:authtype.type_hardened',
|
||||
value: 'hardened'
|
||||
},
|
||||
{
|
||||
label: '@i18n:authtype.type_idp',
|
||||
value: 'idp'
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
|
||||
@@ -171,7 +171,8 @@ return {
|
||||
{ label: '@i18n:authtype.type_radius', value: 'radius' },
|
||||
{ label: '@i18n:authtype.type_otp', value: 'otp' },
|
||||
{ label: '@i18n:authtype.type_pkinit', value: 'pkinit' },
|
||||
{ label: '@i18n:authtype.type_hardened', value: 'hardened' }
|
||||
{ label: '@i18n:authtype.type_hardened', value: 'hardened' },
|
||||
{ label: '@i18n:authtype.type_idp', value: 'idp' }
|
||||
],
|
||||
tooltip: '@i18n:authtype.user_tooltip'
|
||||
},
|
||||
|
||||
@@ -239,7 +239,8 @@ return {
|
||||
{ label: '@i18n:authtype.type_radius', value: 'radius' },
|
||||
{ label: '@i18n:authtype.type_otp', value: 'otp' },
|
||||
{ label: '@i18n:authtype.type_pkinit', value: 'pkinit' },
|
||||
{ label: '@i18n:authtype.type_hardened', value: 'hardened' }
|
||||
{ label: '@i18n:authtype.type_hardened', value: 'hardened' },
|
||||
{ label: '@i18n:authtype.type_idp', value: 'idp' }
|
||||
],
|
||||
tooltip: {
|
||||
title: '@i18n:authtype.user_tooltip',
|
||||
@@ -256,6 +257,17 @@ return {
|
||||
{
|
||||
name: 'ipatokenradiususername',
|
||||
flags: ['w_if_no_aci']
|
||||
},
|
||||
{
|
||||
$type: 'entity_select',
|
||||
name: 'ipaidpconfiglink',
|
||||
flags: ['w_if_no_aci'],
|
||||
other_entity: 'idp',
|
||||
other_field: 'cn'
|
||||
},
|
||||
{
|
||||
name: 'ipaidpsub',
|
||||
flags: ['w_if_no_aci']
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"type_radius": "RADIUS",
|
||||
"type_pkinit": "PKINIT",
|
||||
"type_hardened": "Hardened Password (by SPAKE or FAST)",
|
||||
"type_idp": "External Identity Provider",
|
||||
"user_tooltip": "<p>Per-user setting, overwrites the global setting if any option is checked.</p><p><strong>Password + Two-factor:</strong> LDAP and Kerberos allow authentication with either one of the authentication types but Kerberos uses pre-authentication method which requires to use armor ccache.</p><p><strong>RADIUS with another type:</strong> Kerberos always use RADIUS, but LDAP never does. LDAP only recognize the password and two-factor authentication options.</p>"
|
||||
},
|
||||
"buttons": {
|
||||
|
||||
@@ -191,6 +191,7 @@ class i18n_messages(Command):
|
||||
"type_radius": _("RADIUS"),
|
||||
"type_pkinit": _("PKINIT"),
|
||||
"type_hardened": _("Hardened Password (by SPAKE or FAST)"),
|
||||
"type_idp": _("External Identity Provider"),
|
||||
"type_disabled": _("Disable per-user override"),
|
||||
"user_tooltip": _("<p>Per-user setting, overwrites the global setting if any option is checked.</p><p><strong>Password + Two-factor:</strong> LDAP and Kerberos allow authentication with either one of the authentication types but Kerberos uses pre-authentication method which requires to use armor ccache.</p><p><strong>RADIUS with another type:</strong> Kerberos always use RADIUS, but LDAP never does. LDAP only recognize the password and two-factor authentication options.</p>"),
|
||||
},
|
||||
@@ -1091,6 +1092,16 @@ class i18n_messages(Command):
|
||||
"Remove host groups from host group '${primary_key}'"
|
||||
),
|
||||
},
|
||||
"idp": {
|
||||
"template_keycloak": _("Keycloak or Red Hat SSO"),
|
||||
"template_google": _("Google"),
|
||||
"template_github": _("Github"),
|
||||
"template_microsoft": _("Microsoft or Azure"),
|
||||
"template_okta": _("Okta"),
|
||||
"label_idpclient": _("OAuth 2.0 client details"),
|
||||
"label_idp": _("Identity provider details"),
|
||||
"verify_secret": _("Verify secret"),
|
||||
},
|
||||
"idoverrideuser": {
|
||||
"anchor_label": _("User to override"),
|
||||
"anchor_tooltip": _("Enter trusted or IPA user login. Note: search doesn't list users from trusted domains."),
|
||||
|
||||
Reference in New Issue
Block a user