feat(ce): fix small issues for Highlight Business Edition feature (#6043)

* feat(logs): added orange border to export button.

* fix(ldap): fixed connectivity check radio button alignment issue

* fix(be-feature): added box shadow to limited feature border

* fix(be-feature): fixed hide internal auth toggle issue.

* feat(ldap): added isLimitedFeatureSelfContained config to ldap-custom-admin-group and test-login components

* feat(auth): moved save settings button in auth page to a component.

* feat(oauth): use saveSettingsButton component in oauth

* feat(oauth): use saveSettingsButton component in internal auth

* feat(oauth): use saveSettingsButton component in MS active directory auth

* feat(oauth): use saveSettingsButton component in ldap auth

* feat(auth): added new component to index.js

* removed css inline styles.

* feat(ad): added disable check method to ad auth.

* feat(ldap): moved save settings disable method to parent component

* removed inline styles.

* fix(ldap): fixed test login misalignment issue

* fix(ldap): pass isLdapFormValid function from page component

* fix(ldap): made the toggle button in custom admin group orange when it's limited to CE.

* fix(auth): fixed save setting button in Microsoft Active Directory auth

* fix(ldap): made the assign admin toggle bright orange
pull/6167/head
fhanportainer 2021-11-29 10:41:21 +13:00 committed by GitHub
parent 18c323185e
commit 3f31d4b00b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 328 additions and 97 deletions

View File

@ -845,6 +845,54 @@ json-tree .branch-preview {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.m-l-5 {
margin-left: 5px;
}
.m-l-20 {
margin-left: 20px;
}
.m-l-30 {
margin-left: 30px;
}
.m-r-2 {
margin-right: 2px;
}
.m-r-5 {
margin-right: 5px;
}
.m-t-10 {
margin-top: 10px;
}
.m-t-10 {
margin-top: 10px;
}
.m-b-10 {
margin-bottom: 10px;
}
.no-margin {
margin: 0 !important;
}
.no-border {
border: none;
}
.dispay-flex {
display: flex;
}
.flex-wrap {
flex-wrap: wrap;
}
.text-wrap { .text-wrap {
word-break: break-all; word-break: break-all;
white-space: normal; white-space: normal;

View File

@ -6,15 +6,22 @@
border-color: var(--border-form-control-color); border-color: var(--border-form-control-color);
} }
button.limited-be { button.limited-be,
button[disabled].limited-be.oauth-save-settings-button {
background-color: var(--BE-only); background-color: var(--BE-only);
border-color: var(--BE-only); border-color: var(--BE-only);
} }
button.limited-be.oauth-save-settings-button {
background-color: var(--blue-2);
border-color: transparent;
}
ng-form.limited-be, ng-form.limited-be,
form.limited-be, form.limited-be,
div.limited-be { div.limited-be {
border: solid 1px var(--BE-only); border: solid 1px var(--BE-only);
box-shadow: var(--shadow-boxselector-color);
padding: 10px; padding: 10px;
pointer-events: none; pointer-events: none;
touch-action: none; touch-action: none;

View File

@ -6,6 +6,8 @@ angular.module('portainer.oauth').component('oauthSettings', {
bindings: { bindings: {
settings: '=', settings: '=',
teams: '<', teams: '<',
onSaveSettings: '<',
saveButtonState: '<',
}, },
controller, controller,
}); });

View File

@ -8,6 +8,7 @@ export default class OAuthSettingsController {
this.featureService = featureService; this.featureService = featureService;
this.limitedFeature = HIDE_INTERNAL_AUTH; this.limitedFeature = HIDE_INTERNAL_AUTH;
this.limitedFeatureClass = 'limited-be';
this.state = { this.state = {
provider: 'custom', provider: 'custom',
@ -61,7 +62,7 @@ export default class OAuthSettingsController {
} }
updateSSO() { updateSSO() {
this.settings.HideInternalAuth = this.settings.SSO; this.settings.HideInternalAuth = this.featureService.isLimitedToBE(this.limitedFeature) ? false : this.settings.SSO;
} }
addTeamMembershipMapping() { addTeamMembershipMapping() {
@ -72,6 +73,20 @@ export default class OAuthSettingsController {
this.settings.TeamMemberships.OAuthClaimMappings.splice(index, 1); this.settings.TeamMemberships.OAuthClaimMappings.splice(index, 1);
} }
isOAuthTeamMembershipFormValid() {
if (this.settings.OAuthAutoMapTeamMemberships && this.settings.TeamMemberships) {
if (!this.settings.TeamMemberships.OAuthClaimName) {
return false;
}
const hasInvalidMapping = this.settings.TeamMemberships.OAuthClaimMappings.some((m) => !(m.ClaimValRegex && m.Team));
if (hasInvalidMapping) {
return false;
}
}
return true;
}
$onInit() { $onInit() {
this.isLimitedToBE = this.featureService.isLimitedToBE(this.limitedFeature); this.isLimitedToBE = this.featureService.isLimitedToBE(this.limitedFeature);

View File

@ -1,4 +1,4 @@
<ng-form name="$ctrl.oauthSettingsForm"> <ng-form name="oauthSettingsForm">
<div class="col-sm-12 form-section-title"> <div class="col-sm-12 form-section-title">
Single Sign-On Single Sign-On
</div> </div>
@ -379,4 +379,12 @@
</a> </a>
</div> </div>
</div> </div>
<save-auth-settings-button
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
save-button-disabled="!$ctrl.isOAuthTeamMembershipFormValid() || oauthSettingsForm.$invalid"
limited-feature-id="$ctrl.limitedFeature"
limited-feature-class="$ctrl.limitedFeatureClass"
class-name="'oauth-save-settings-button'"
></save-auth-settings-button>
</ng-form> </ng-form>

View File

@ -1,10 +1,11 @@
import angular from 'angular'; import angular from 'angular';
import ldapModule from './ldap'; import ldapModule from './ldap';
import { autoUserProvisionToggle } from './auto-user-provision-toggle'; import { autoUserProvisionToggle } from './auto-user-provision-toggle';
import { saveAuthSettingsButton } from './save-auth-settings-button';
import { internalAuth } from './internal-auth';
export default angular export default angular
.module('portainer.settings.authentication', [ldapModule]) .module('portainer.settings.authentication', [ldapModule])
.component('internalAuth', internalAuth)
.component('saveAuthSettingsButton', saveAuthSettingsButton)
.component('autoUserProvisionToggle', autoUserProvisionToggle).name; .component('autoUserProvisionToggle', autoUserProvisionToggle).name;

View File

@ -0,0 +1,7 @@
export const internalAuth = {
templateUrl: './internal-auth.html',
bindings: {
onSaveSettings: '<',
saveButtonState: '<',
},
};

View File

@ -0,0 +1,8 @@
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group col-sm-12 text-muted small">
When using internal authentication, Portainer will encrypt user passwords and store credentials locally.
</div>
<save-auth-settings-button on-save-settings="($ctrl.onSaveSettings)" save-button-state="($ctrl.saveButtonState)"></save-auth-settings-button>

View File

@ -3,8 +3,9 @@ import { HIDE_INTERNAL_AUTH } from '@/portainer/feature-flags/feature-ids';
export default class AdSettingsController { export default class AdSettingsController {
/* @ngInject */ /* @ngInject */
constructor(LDAPService) { constructor(LDAPService, featureService) {
this.LDAPService = LDAPService; this.LDAPService = LDAPService;
this.featureService = featureService;
this.domainSuffix = ''; this.domainSuffix = '';
this.limitedFeatureId = HIDE_INTERNAL_AUTH; this.limitedFeatureId = HIDE_INTERNAL_AUTH;
@ -55,6 +56,10 @@ export default class AdSettingsController {
this.settings.URLs.splice(index, 1); this.settings.URLs.splice(index, 1);
} }
isSaveSettingButtonDisabled() {
return this.featureService.isLimitedToBE(this.limitedFeatureId) || !this.isLdapFormValid();
}
$onInit() { $onInit() {
this.tlscaCert = this.settings.TLSCACert; this.tlscaCert = this.settings.TLSCACert;
this.parseDomainName(this.settings.ReaderDN); this.parseDomainName(this.settings.ReaderDN);

View File

@ -160,7 +160,14 @@
selected-admin-groups="$ctrl.selectedAdminGroups" selected-admin-groups="$ctrl.selectedAdminGroups"
default-admin-group-search-filter="'(objectClass=groupOfNames)'" default-admin-group-search-filter="'(objectClass=groupOfNames)'"
limited-feature-id="$ctrl.limitedFeatureId" limited-feature-id="$ctrl.limitedFeatureId"
is-limited-feature-self-contained="false"
></ldap-custom-admin-group> ></ldap-custom-admin-group>
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId"></ldap-settings-test-login> <ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId" is-limited-feature-self-contained="false"></ldap-settings-test-login>
<save-auth-settings-button
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
limited-feature-id="$ctrl.limitedFeatureId"
save-button-disabled="($ctrl.isSaveSettingButtonDisabled())"
></save-auth-settings-button>
</ng-form> </ng-form>

View File

@ -8,5 +8,8 @@ export const adSettings = {
tlscaCert: '=', tlscaCert: '=',
state: '=', state: '=',
connectivityCheck: '<', connectivityCheck: '<',
onSaveSettings: '<',
saveButtonState: '<',
isLdapFormValid: '&?',
}, },
}; };

View File

@ -1,10 +1,10 @@
<div class="form-group"> <div class="form-group">
<label for="ldap_password" class="col-sm-3 control-label text-left"> <label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Connectivity check Connectivity check
<i class="fa fa-check green-icon" style="margin-left: 5px;" ng-if="$ctrl.state.successfulConnectivityCheck"></i> <i class="fa fa-check green-icon m-l-5" ng-if="$ctrl.state.successfulConnectivityCheck"></i>
<i class="fa fa-times red-icon" style="margin-left: 5px;" ng-if="$ctrl.state.failedConnectivityCheck"></i> <i class="fa fa-times red-icon m-l-5" ng-if="$ctrl.state.failedConnectivityCheck"></i>
</label> </label>
<div class="col-sm-9"> <div class="col-sm-9 col-lg-10">
<button <button
type="button" type="button"
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"

View File

@ -1,3 +1,4 @@
import './ldap-custom-admin-group.css';
import controller from './ldap-custom-admin-group.controller'; import controller from './ldap-custom-admin-group.controller';
export const ldapCustomAdminGroup = { export const ldapCustomAdminGroup = {
@ -9,5 +10,6 @@ export const ldapCustomAdminGroup = {
defaultAdminGroupSearchFilter: '<', defaultAdminGroupSearchFilter: '<',
onSearchClick: '<', onSearchClick: '<',
limitedFeatureId: '<', limitedFeatureId: '<',
isLimitedFeatureSelfContained: '<',
}, },
}; };

View File

@ -0,0 +1,4 @@
.ldap-custom-admin-group-title {
float: initial;
margin-top: 30px;
}

View File

@ -1,10 +1,10 @@
<div class="col-sm-12 form-section-title" style="float: initial;"> <div class="col-sm-12 form-section-title ldap-custom-admin-group-title" ng-style="$ctrl.isLimitedFeatureSelfContained && { 'padding-bottom': '15px' }">
Auto-populate team admins Auto-populate team admins <be-feature-indicator feature="$ctrl.limitedFeatureId" class="space-left" ng-if="$ctrl.isLimitedFeatureSelfContained"></be-feature-indicator>
</div> </div>
<rd-widget ng-repeat="config in $ctrl.settings.AdminGroupSearchSettings | limitTo: (1 - $ctrl.settings.AdminGroupSearchSettings)" style="display: block; margin-bottom: 10px;"> <rd-widget ng-repeat="config in $ctrl.settings.AdminGroupSearchSettings | limitTo: (1 - $ctrl.settings.AdminGroupSearchSettings)">
<rd-widget-body> <rd-widget-body>
<div class="form-group" ng-if="$index > 0" style="margin-bottom: 10px;"> <div class="form-group m-b-10" ng-if="$index > 0">
<span class="col-sm-12 text-muted small"> <span class="col-sm-12 text-muted small">
Extra search configuration Extra search configuration
</span> </span>
@ -16,7 +16,17 @@
<portainer-tooltip position="bottom" message="The distinguished name of the element from which the LDAP server will search for groups."></portainer-tooltip> <portainer-tooltip position="bottom" message="The distinguished name of the element from which the LDAP server will search for groups."></portainer-tooltip>
</label> </label>
<div class="col-sm-8 col-md-4"> <div class="col-sm-8 col-md-4">
<input type="text" class="form-control" id="ldap_admin_group_basedn_{{ $index }}" ng-model="config.GroupBaseDN" placeholder="dc=ldap,dc=domain,dc=tld" /> <input
type="text"
class="form-control"
id="ldap_admin_group_basedn_{{ $index }}"
ng-model="config.GroupBaseDN"
placeholder="dc=ldap,dc=domain,dc=tld"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
limited-feature-tabindex="-1"
/>
</div> </div>
<label for="ldap_admin_group_att_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left"> <label for="ldap_admin_group_att_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
@ -24,7 +34,17 @@
<portainer-tooltip position="bottom" message="LDAP attribute which denotes the group membership."></portainer-tooltip> <portainer-tooltip position="bottom" message="LDAP attribute which denotes the group membership."></portainer-tooltip>
</label> </label>
<div class="col-sm-8 col-md-4"> <div class="col-sm-8 col-md-4">
<input type="text" class="form-control" id="ldap_admin_group_att_{{ $index }}" ng-model="config.GroupAttribute" placeholder="member" /> <input
type="text"
class="form-control"
id="ldap_admin_group_att_{{ $index }}"
ng-model="config.GroupAttribute"
placeholder="member"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
limited-feature-tabindex="-1"
/>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -33,7 +53,17 @@
<portainer-tooltip position="bottom" message="The LDAP search filter used to select group elements, optional."></portainer-tooltip> <portainer-tooltip position="bottom" message="The LDAP search filter used to select group elements, optional."></portainer-tooltip>
</label> </label>
<div ng-class="{ 'col-sm-7 col-md-9': $index, 'col-sm-8 col-md-10': !$index }"> <div ng-class="{ 'col-sm-7 col-md-9': $index, 'col-sm-8 col-md-10': !$index }">
<input type="text" class="form-control" id="ldap_admin_group_filter_{{ $index }}" ng-model="config.GroupFilter" placeholder="(objectClass=groupOfNames)" /> <input
type="text"
class="form-control"
id="ldap_admin_group_filter_{{ $index }}"
ng-model="config.GroupFilter"
placeholder="(objectClass=groupOfNames)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
limited-feature-tabindex="-1"
/>
</div> </div>
<div class="col-sm-1" ng-if="$index > 0"> <div class="col-sm-1" ng-if="$index > 0">
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($index)"> <button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($index)">
@ -44,20 +74,31 @@
</rd-widget-body> </rd-widget-body>
</rd-widget> </rd-widget>
<div class="form-group" style="margin-top: 10px;"> <div class="form-group m-t-10">
<div class="col-sm-12"> <div class="col-sm-12">
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()"> <button
class="label label-default interactive no-border"
ng-click="$ctrl.onAddClick()"
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
limited-feature-disabled
>
<i class="fa fa-plus-circle" aria-hidden="true"></i> add group search configuration <i class="fa fa-plus-circle" aria-hidden="true"></i> add group search configuration
</button> </button>
</div> </div>
<div class="col-sm-12" style="margin-top: 10px;"> <div class="col-sm-12 m-t-10">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()" limited-feature-dir="{{ $ctrl.limitedFeatureId }}" limited-feature-tabindex="-1"> <button
class="btn btm-sm btn-primary"
type="button"
ng-click="$ctrl.search()"
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
limited-feature-tabindex="-1"
>
Fetch Admin Group(s) Fetch Admin Group(s)
</button> </button>
<be-feature-indicator feature="$ctrl.limitedFeatureId" class="space-left"></be-feature-indicator> <span ng-if="$ctrl.groups && $ctrl.groups.length === 0" class="m-l-30"> <i class="fa fa-exclamation-triangle text-warning" aria-hidden="true"></i> No groups found</span>
<span ng-if="$ctrl.groups && $ctrl.groups.length === 0" style="margin-left: 30px;">
<i class="fa fa-exclamation-triangle text-warning" aria-hidden="true"></i> No groups found</span
>
</div> </div>
</div> </div>
@ -66,7 +107,7 @@
<label for="admin-auto-populate" class="control-label text-left text-muted" ng-class="{ 'text-muted': !$ctrl.enableAssignAdminGroup }"> <label for="admin-auto-populate" class="control-label text-left text-muted" ng-class="{ 'text-muted': !$ctrl.enableAssignAdminGroup }">
Assign admin rights to group(s) Assign admin rights to group(s)
</label> </label>
<label class="switch" style="margin-left: 20px;"> <label class="switch m-l-20" ng-class="{ 'business limited': $ctrl.isLimitedFeatureSelfContained }">
<input id="admin-auto-populate" ng-disabled="!$ctrl.enableAssignAdminGroup" name="admin-auto-populate" type="checkbox" ng-model="$ctrl.settings.AdminAutoPopulate" /><i></i> <input id="admin-auto-populate" ng-disabled="!$ctrl.enableAssignAdminGroup" name="admin-auto-populate" type="checkbox" ng-model="$ctrl.settings.AdminAutoPopulate" /><i></i>
</label> </label>
</div> </div>
@ -78,6 +119,7 @@
Select Group(s) Select Group(s)
</label> </label>
<span <span
class="m-l-20"
isteven-multi-select isteven-multi-select
ng-if="$ctrl.enableAssignAdminGroup" ng-if="$ctrl.enableAssignAdminGroup"
input-model="$ctrl.groups" input-model="$ctrl.groups"
@ -88,7 +130,6 @@
helper-elements="filter" helper-elements="filter"
search-property="name" search-property="name"
translation="{nothingSelected: 'Select one or more groups', search: 'Search...'}" translation="{nothingSelected: 'Select one or more groups', search: 'Search...'}"
style="margin-left: 20px;"
> >
</span> </span>
</div> </div>

View File

@ -1,3 +1,4 @@
import './ldap-settings-custom.css';
import controller from './ldap-settings-custom.controller'; import controller from './ldap-settings-custom.controller';
export const ldapSettingsCustom = { export const ldapSettingsCustom = {
@ -11,5 +12,8 @@ export const ldapSettingsCustom = {
connectivityCheck: '<', connectivityCheck: '<',
onSearchUsersClick: '<', onSearchUsersClick: '<',
onSearchGroupsClick: '<', onSearchGroupsClick: '<',
onSaveSettings: '<',
saveButtonState: '<',
saveButtonDisabled: '<',
}, },
}; };

View File

@ -1,5 +1,4 @@
import { EXTERNAL_AUTH_LDAP } from '@/portainer/feature-flags/feature-ids'; import { EXTERNAL_AUTH_LDAP } from '@/portainer/feature-flags/feature-ids';
export default class LdapSettingsCustomController { export default class LdapSettingsCustomController {
constructor() { constructor() {
this.limitedFeatureId = EXTERNAL_AUTH_LDAP; this.limitedFeatureId = EXTERNAL_AUTH_LDAP;

View File

@ -0,0 +1,3 @@
label[for='anonymous_mode'].control-label {
padding-top: 0;
}

View File

@ -14,7 +14,7 @@
<div class="form-group"> <div class="form-group">
<div class="col-sm-12 small text-muted"> <div class="col-sm-12 small text-muted">
<p> <p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-info-circle blue-icon m-r-2" aria-hidden="true"></i>
You can configure multiple LDAP Servers for authentication fallback. Make sure all servers are using the same configuration (i.e. if TLS is enabled, they should all use the You can configure multiple LDAP Servers for authentication fallback. Make sure all servers are using the same configuration (i.e. if TLS is enabled, they should all use the
same certificates). same certificates).
</p> </p>
@ -22,12 +22,11 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left" style="display: flex; flex-wrap: wrap;"> <label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left dispay-flex flex-wrap">
LDAP Server LDAP Server
<button <button
type="button" type="button"
class="label label-default interactive" class="label label-default interactive no-border"
style="border: 0;"
ng-click="$ctrl.addLDAPUrl()" ng-click="$ctrl.addLDAPUrl()"
limited-feature-dir="{{ $ctrl.limitedFeatureId }}" limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
limited-feature-disabled limited-feature-disabled
@ -37,7 +36,7 @@
</button> </button>
</label> </label>
<div class="col-sm-9 col-lg-10"> <div class="col-sm-9 col-lg-10">
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;"> <div class="m-b-10 dispay-flex" ng-repeat="url in $ctrl.settings.URLs track by $index">
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" required /> <input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" required />
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)"> <button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
<i class="fa fa-trash" aria-hidden="true"></i> <i class="fa fa-trash" aria-hidden="true"></i>
@ -46,14 +45,15 @@
</div> </div>
</div> </div>
<!-- Anonymous mode-->
<div class="form-group"> <div class="form-group">
<div class="col-sm-12"> <label for="anonymous_mode" class="control-label text-left col-sm-3 col-lg-2">
<label for="anonymous_mode" class="control-label text-left">
Anonymous mode Anonymous mode
<portainer-tooltip position="bottom" message="Enable this option if the server is configured for Anonymous access."></portainer-tooltip> <portainer-tooltip position="bottom" message="Enable this option if the server is configured for Anonymous access."></portainer-tooltip>
</label> </label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" id="anonymous_mode" ng-model="$ctrl.settings.AnonymousMode" /><i></i> </label> <div class="col-sm-9 col-lg-10">
<label class="switch">
<input type="checkbox" id="anonymous_mode" ng-model="$ctrl.settings.AnonymousMode" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" /><i></i>
</label>
</div> </div>
</div> </div>
<!-- !Anonymous mode--> <!-- !Anonymous mode-->
@ -102,29 +102,38 @@
></ldap-connectivity-check> ></ldap-connectivity-check>
<ldap-custom-user-search <ldap-custom-user-search
style="margin-top: 5px;" class="m-r-5"
settings="$ctrl.settings.SearchSettings" settings="$ctrl.settings.SearchSettings"
on-search-click="($ctrl.onSearchUsersClick)" on-search-click="($ctrl.onSearchUsersClick)"
limited-feature-id="$ctrl.limitedFeatureId" limited-feature-id="$ctrl.limitedFeatureId"
></ldap-custom-user-search> ></ldap-custom-user-search>
<ldap-custom-group-search <ldap-custom-group-search
style="margin-top: 5px;" class="m-r-5"
settings="$ctrl.settings.GroupSearchSettings" settings="$ctrl.settings.GroupSearchSettings"
on-search-click="($ctrl.onSearchGroupsClick)" on-search-click="($ctrl.onSearchGroupsClick)"
limited-feature-id="$ctrl.limitedFeatureId" limited-feature-id="$ctrl.limitedFeatureId"
></ldap-custom-group-search> ></ldap-custom-group-search>
<div limited-feature-dir="{{ $ctrl.limitedFeatureId }}" limited-feature-class="limited-be" style="margin-bottom: 20px;"> <ldap-custom-admin-group
<ldap-custom-admin-group class="m-r-5"
style="margin-top: 5px;"
settings="$ctrl.settings" settings="$ctrl.settings"
on-search-click="($ctrl.onSearchAdminGroupsClick)" on-search-click="($ctrl.onSearchAdminGroupsClick)"
selected-admin-groups="$ctrl.selectedAdminGroups" selected-admin-groups="$ctrl.selectedAdminGroups"
default-admin-group-search-filter="'(objectClass=groupOfNames)'" default-admin-group-search-filter="'(objectClass=groupOfNames)'"
limited-feature-id="$ctrl.limitedFeatureId" limited-feature-id="$ctrl.limitedFeatureId"
></ldap-custom-admin-group> is-limited-feature-self-contained="true"
</div> ></ldap-custom-admin-group>
<div limited-feature-dir="{{ $ctrl.limitedFeatureId }}" limited-feature-class="limited-be"> <ldap-settings-test-login
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId" show-be-indicator-if-needed="true"></ldap-settings-test-login> settings="$ctrl.settings"
</div> limited-feature-id="$ctrl.limitedFeatureId"
show-be-indicator-if-needed="true"
is-limited-feature-self-contained="true"
></ldap-settings-test-login>
<save-auth-settings-button
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
save-button-disabled="!$ctrl.saveButtonDisabled()"
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
></save-auth-settings-button>

View File

@ -8,9 +8,11 @@ export const ldapSettingsOpenLdap = {
tlscaCert: '=', tlscaCert: '=',
state: '=', state: '=',
connectivityCheck: '<', connectivityCheck: '<',
onTlscaCertChange: '<', onTlscaCertChange: '<',
onSearchUsersClick: '<', onSearchUsersClick: '<',
onSearchGroupsClick: '<', onSearchGroupsClick: '<',
onSaveSettings: '<',
saveButtonState: '<',
saveButtonDisabled: '<',
}, },
}; };

View File

@ -184,4 +184,10 @@
></ldap-group-search> ></ldap-group-search>
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId"></ldap-settings-test-login> <ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId"></ldap-settings-test-login>
<save-auth-settings-button
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
save-button-disabled="!$ctrl.saveButtonDisabled()"
limited-feature-id="$ctrl.limitedFeatureId"
></save-auth-settings-button>
</ng-form> </ng-form>

View File

@ -1,3 +1,4 @@
import './ldap-settings-test-login.css';
import controller from './ldap-settings-test-login.controller'; import controller from './ldap-settings-test-login.controller';
export const ldapSettingsTestLogin = { export const ldapSettingsTestLogin = {
@ -7,5 +8,6 @@ export const ldapSettingsTestLogin = {
settings: '=', settings: '=',
limitedFeatureId: '<', limitedFeatureId: '<',
showBeIndicatorIfNeeded: '<', showBeIndicatorIfNeeded: '<',
isLimitedFeatureSelfContained: '<',
}, },
}; };

View File

@ -0,0 +1,4 @@
label[for='ldap_test_password'] {
font-size: 0.9em;
margin-right: 5px;
}

View File

@ -1,16 +1,31 @@
<div class="col-sm-12 form-section-title"> <div class="col-sm-12 form-section-title" ng-style="$ctrl.isLimitedFeatureSelfContained && { 'padding-bottom': '15px' }">
Test login Test login
<be-feature-indicator
ng-if="$ctrl.showBeIndicatorIfNeeded"
feature="$ctrl.limitedFeatureId"
class="space-left"
ng-if="$ctrl.isLimitedFeatureSelfContained"
></be-feature-indicator>
</div> </div>
<div class="form-inline"> <div class="form-inline">
<div class="form-group" style="margin: 0;"> <div class="form-group" style="margin: 0;">
<label for="ldap_test_username" style="font-size: 0.9em; margin-right: 5px;"> <label for="ldap_test_username" style="font-size: 0.9em; margin-right: 5px;">
Username Username
</label> </label>
<input type="text" class="form-control" id="ldap_test_username" ng-model="$ctrl.username" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" /> <input
type="text"
class="form-control"
id="ldap_test_username"
ng-model="$ctrl.username"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
limited-feature-tabindex="-1"
/>
</div> </div>
<div class="form-group" style="margin: 0;"> <div class="form-group no-margin">
<label for="ldap_test_password" style="font-size: 0.9em; margin-right: 5px;"> <label for="ldap_test_password">
Password Password
</label> </label>
<input <input
@ -20,18 +35,20 @@
ng-model="$ctrl.password" ng-model="$ctrl.password"
autocomplete="new-password" autocomplete="new-password"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
limited-feature-tabindex="-1" limited-feature-tabindex="-1"
/> />
</div> </div>
<div class="form-group" style="margin: 0;"> <div class="form-group no-margin">
<button <button
type="submit" type="submit"
class="btn btn-primary" class="btn btn-primary"
ng-disabled="$ctrl.state.testStatus === $ctrl.TEST_STATUS.LOADING || !$ctrl.username || !$ctrl.password" ng-disabled="$ctrl.state.testStatus === $ctrl.TEST_STATUS.LOADING || !$ctrl.username || !$ctrl.password"
ng-click="$ctrl.testLogin($ctrl.username, $ctrl.password)" ng-click="$ctrl.testLogin($ctrl.username, $ctrl.password)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-disabled limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
limited-feature-tabindex="-1" limited-feature-tabindex="-1"
> >
<span ng-if="$ctrl.state.testStatus !== $ctrl.TEST_STATUS.LOADING">Test</span> <span ng-if="$ctrl.state.testStatus !== $ctrl.TEST_STATUS.LOADING">Test</span>
@ -40,6 +57,4 @@
<i ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.SUCCESS" class="fa fa-check green-icon"></i> <i ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.SUCCESS" class="fa fa-check green-icon"></i>
<i ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.FAILURE" class="fa fa-times red-icon"></i> <i ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.FAILURE" class="fa fa-times red-icon"></i>
</div> </div>
<be-feature-indicator ng-if="$ctrl.showBeIndicatorIfNeeded" feature="$ctrl.limitedFeatureId" class="space-left"></be-feature-indicator>
</div> </div>

View File

@ -7,5 +7,8 @@ export const ldapSettings = {
settings: '=', settings: '=',
state: '<', state: '<',
connectivityCheck: '<', connectivityCheck: '<',
onSaveSettings: '<',
saveButtonState: '<',
isLdapFormValid: '<',
}, },
}; };

View File

@ -27,6 +27,9 @@
connectivity-check="$ctrl.connectivityCheck" connectivity-check="$ctrl.connectivityCheck"
on-search-users-click="($ctrl.searchUsers)" on-search-users-click="($ctrl.searchUsers)"
on-search-groups-click="($ctrl.searchGroups)" on-search-groups-click="($ctrl.searchGroups)"
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
save-button-disabled="$ctrl.isLdapFormValid"
></ldap-settings-custom> ></ldap-settings-custom>
<ldap-settings-open-ldap <ldap-settings-open-ldap
ng-if="$ctrl.settings.ServerType === $ctrl.SERVER_TYPES.OPEN_LDAP" ng-if="$ctrl.settings.ServerType === $ctrl.SERVER_TYPES.OPEN_LDAP"
@ -37,5 +40,8 @@
connectivity-check="$ctrl.connectivityCheck" connectivity-check="$ctrl.connectivityCheck"
on-search-users-click="($ctrl.searchUsers)" on-search-users-click="($ctrl.searchUsers)"
on-search-groups-click="($ctrl.searchGroups)" on-search-groups-click="($ctrl.searchGroups)"
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
save-button-disabled="$ctrl.isLdapFormValid"
></ldap-settings-open-ldap> ></ldap-settings-open-ldap>
</div> </div>

View File

@ -0,0 +1,11 @@
export const saveAuthSettingsButton = {
templateUrl: './save-auth-settings-button.html',
bindings: {
onSaveSettings: '<',
saveButtonDisabled: '<',
saveButtonState: '<',
limitedFeatureId: '<',
limitedFeatureClass: '<',
className: '<',
},
};

View File

@ -0,0 +1,19 @@
<div class="col-sm-12 form-section-title">
Actions
</div>
<div class="form-group">
<div class="col-sm-12">
<button
type="button"
ng-class="[$ctrl.className, 'btn btn-primary btn-sm']"
ng-click="$ctrl.onSaveSettings()"
ng-disabled="$ctrl.saveButtonDisabled || $ctrl.saveButtonState"
button-spinner="$ctrl.saveButtonState"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-class="{{::$ctrl.limitedFeatureClass}}"
>
<span ng-hide="$ctrl.saveButtonState">Save settings</span>
<span ng-show="$ctrl.saveButtonState">Saving...</span>
</button>
</div>
</div>

View File

@ -25,7 +25,9 @@
</p> </p>
<div> <div>
<button type="button" class="btn btn-sm btn-primary" style="margin-right: 10px;"> <i class="fa fa-download space-right" aria-hidden="true"></i>Export as CSV </button> <button type="button" class="btn btn-sm btn-primary m-r-10" limited-feature-dir="{{::$ctrl.limitedFeature}}" limited-feature-class="limited-be" limited-feature-disabled>
<i class="fa fa-download space-right" aria-hidden="true"></i>Export as CSV
</button>
<be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator> <be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator>
</div> </div>
</rd-widget-body> </rd-widget-body>

View File

@ -25,7 +25,9 @@
</p> </p>
<div> <div>
<button type="button" class="btn btn-sm btn-primary" style="margin-right: 10px;"><i class="fa fa-download space-right" aria-hidden="true"></i>Export as CSV </button> <button type="button" class="btn btn-sm btn-primary m-r-10" limited-feature-dir="{{::$ctrl.limitedFeature}}" limited-feature-class="limited-be" limited-feature-disabled
><i class="fa fa-download space-right" aria-hidden="true"></i>Export as CSV
</button>
<be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator> <be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator>
</div> </div>
</rd-widget-body> </rd-widget-body>

View File

@ -37,14 +37,7 @@
<box-selector radio-name="authOptions" ng-model="authMethod" options="authOptions" on-change="(onChangeAuthMethod)"></box-selector> <box-selector radio-name="authOptions" ng-model="authMethod" options="authOptions" on-change="(onChangeAuthMethod)"></box-selector>
<div ng-if="authenticationMethodSelected(1)"> <internal-auth ng-if="authenticationMethodSelected(1)" on-save-settings="(saveSettings)" save-button-state="state.actionInProgress"></internal-auth>
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group col-sm-12 text-muted small">
When using internal authentication, Portainer will encrypt user passwords and store credentials locally.
</div>
</div>
<ldap-settings <ldap-settings
ng-if="authenticationMethodSelected(2)" ng-if="authenticationMethodSelected(2)"
@ -52,6 +45,9 @@
tlsca-cert="formValues.TLSCACert" tlsca-cert="formValues.TLSCACert"
state="state" state="state"
connectivity-check="LDAPConnectivityCheck" connectivity-check="LDAPConnectivityCheck"
on-save-settings="(saveSettings)"
save-button-state="state.actionInProgress"
is-ldap-form-valid="isLDAPFormValid"
></ldap-settings> ></ldap-settings>
<ad-settings <ad-settings
@ -60,29 +56,18 @@
tlsca-cert="formValues.TLSCACert" tlsca-cert="formValues.TLSCACert"
state="state" state="state"
connectivity-check="LDAPConnectivityCheck" connectivity-check="LDAPConnectivityCheck"
on-save-settings="(saveSettings)"
save-button-state="state.actionInProgress"
is-ldap-form-valid="isLDAPFormValid()"
></ad-settings> ></ad-settings>
<oauth-settings ng-if="authenticationMethodSelected(3)" settings="OAuthSettings" teams="teams"></oauth-settings> <oauth-settings
ng-if="authenticationMethodSelected(3)"
<!-- actions --> settings="OAuthSettings"
<div class="col-sm-12 form-section-title"> teams="teams"
Actions on-save-settings="(saveSettings)"
</div> save-button-state="state.actionInProgress"
<div class="form-group"> ></oauth-settings>
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm"
ng-click="saveSettings()"
ng-disabled="state.actionInProgress || (settings.AuthenticationMethod === 2 && !isLDAPFormValid()) || (settings.AuthenticationMethod === 3 && !isOAuthTeamMembershipFormValid()) || authSettingsForm.$invalid"
button-spinner="state.actionInProgress"
>
<span ng-hide="state.actionInProgress">Save settings</span>
<span ng-show="state.actionInProgress">Saving...</span>
</button>
</div>
</div>
<!-- !actions -->
</form> </form>
</rd-widget-body> </rd-widget-body>
</rd-widget> </rd-widget>

View File

@ -182,7 +182,8 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
return ( return (
_.compact(ldapSettings.URLs).length && _.compact(ldapSettings.URLs).length &&
(ldapSettings.AnonymousMode || (ldapSettings.ReaderDN && ldapSettings.Password)) && (ldapSettings.AnonymousMode || (ldapSettings.ReaderDN && ldapSettings.Password)) &&
(!isTLSMode || $scope.formValues.TLSCACert || ldapSettings.TLSConfig.TLSSkipVerify) (!isTLSMode || $scope.formValues.TLSCACert || ldapSettings.TLSConfig.TLSSkipVerify) &&
(!$scope.settings.LDAPSettings.AdminAutoPopulate || ($scope.settings.LDAPSettings.AdminAutoPopulate && $scope.formValues.selectedAdminGroups.length > 0))
); );
} }