44 changed files with 973 additions and 60 deletions
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.exception; |
||||
|
|
||||
|
import org.springframework.http.HttpStatus; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
|
||||
|
public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorResponse { |
||||
|
|
||||
|
private final String resetToken; |
||||
|
|
||||
|
protected ThingsboardCredentialsExpiredResponse(String message, String resetToken) { |
||||
|
super(message, ThingsboardErrorCode.CREDENTIALS_EXPIRED, HttpStatus.UNAUTHORIZED); |
||||
|
this.resetToken = resetToken; |
||||
|
} |
||||
|
|
||||
|
public static ThingsboardCredentialsExpiredResponse of(final String message, final String resetToken) { |
||||
|
return new ThingsboardCredentialsExpiredResponse(message, resetToken); |
||||
|
} |
||||
|
|
||||
|
public String getResetToken() { |
||||
|
return resetToken; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
package org.thingsboard.server.service.security.auth.rest; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import ua_parser.Client; |
||||
|
import ua_parser.Parser; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import java.io.IOException; |
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
@Data |
||||
|
public class RestAuthenticationDetails implements Serializable { |
||||
|
|
||||
|
private final String clientAddress; |
||||
|
private final Client userAgent; |
||||
|
|
||||
|
public RestAuthenticationDetails(HttpServletRequest request) { |
||||
|
this.clientAddress = getClientIP(request); |
||||
|
this.userAgent = getUserAgent(request); |
||||
|
} |
||||
|
|
||||
|
private static String getClientIP(HttpServletRequest request) { |
||||
|
String xfHeader = request.getHeader("X-Forwarded-For"); |
||||
|
if (xfHeader == null) { |
||||
|
return request.getRemoteAddr(); |
||||
|
} |
||||
|
return xfHeader.split(",")[0]; |
||||
|
} |
||||
|
|
||||
|
private static Client getUserAgent(HttpServletRequest request) { |
||||
|
try { |
||||
|
Parser uaParser = new Parser(); |
||||
|
return uaParser.parse(request.getHeader("User-Agent")); |
||||
|
} catch (IOException e) { |
||||
|
return new Client(null, null, null); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
package org.thingsboard.server.service.security.auth.rest; |
||||
|
|
||||
|
import org.springframework.security.authentication.AuthenticationDetailsSource; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
|
||||
|
public class RestAuthenticationDetailsSource implements |
||||
|
AuthenticationDetailsSource<HttpServletRequest, RestAuthenticationDetails> { |
||||
|
|
||||
|
public RestAuthenticationDetails buildDetails(HttpServletRequest context) { |
||||
|
return new RestAuthenticationDetails(context); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.security.exception; |
||||
|
|
||||
|
import org.springframework.security.authentication.CredentialsExpiredException; |
||||
|
|
||||
|
public class UserPasswordExpiredException extends CredentialsExpiredException { |
||||
|
|
||||
|
private final String resetToken; |
||||
|
|
||||
|
public UserPasswordExpiredException(String msg, String resetToken) { |
||||
|
super(msg); |
||||
|
this.resetToken = resetToken; |
||||
|
} |
||||
|
|
||||
|
public String getResetToken() { |
||||
|
return resetToken; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.security.model; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class SecuritySettings { |
||||
|
|
||||
|
private UserPasswordPolicy passwordPolicy; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.security.model; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class UserPasswordPolicy { |
||||
|
|
||||
|
private Integer minimumLength; |
||||
|
private Integer minimumUppercaseLetters; |
||||
|
private Integer minimumLowercaseLetters; |
||||
|
private Integer minimumDigits; |
||||
|
private Integer minimumSpecialCharacters; |
||||
|
|
||||
|
private Integer passwordExpirationPeriodDays; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,155 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.security.system; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.passay.*; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.cache.annotation.CacheEvict; |
||||
|
import org.springframework.cache.annotation.Cacheable; |
||||
|
import org.springframework.security.authentication.BadCredentialsException; |
||||
|
import org.springframework.security.authentication.CredentialsExpiredException; |
||||
|
import org.springframework.security.authentication.DisabledException; |
||||
|
import org.springframework.security.core.AuthenticationException; |
||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.AdminSettings; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.security.UserCredentials; |
||||
|
import org.thingsboard.server.dao.exception.DataValidationException; |
||||
|
import org.thingsboard.server.dao.settings.AdminSettingsService; |
||||
|
import org.thingsboard.server.dao.user.UserService; |
||||
|
import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; |
||||
|
import org.thingsboard.server.service.security.model.SecuritySettings; |
||||
|
import org.thingsboard.server.service.security.model.UserPasswordPolicy; |
||||
|
|
||||
|
import javax.annotation.Resource; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE; |
||||
|
import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTINGS_CACHE; |
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class DefaultSystemSecurityService implements SystemSecurityService { |
||||
|
|
||||
|
private static final ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
|
||||
|
@Autowired |
||||
|
private AdminSettingsService adminSettingsService; |
||||
|
|
||||
|
@Autowired |
||||
|
private BCryptPasswordEncoder encoder; |
||||
|
|
||||
|
@Autowired |
||||
|
private UserService userService; |
||||
|
|
||||
|
@Resource |
||||
|
private SystemSecurityService self; |
||||
|
|
||||
|
@Cacheable(cacheNames = SECURITY_SETTINGS_CACHE, key = "'securitySettings'") |
||||
|
@Override |
||||
|
public SecuritySettings getSecuritySettings(TenantId tenantId) { |
||||
|
SecuritySettings securitySettings = null; |
||||
|
AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, "securitySettings"); |
||||
|
if (adminSettings != null) { |
||||
|
try { |
||||
|
securitySettings = objectMapper.treeToValue(adminSettings.getJsonValue(), SecuritySettings.class); |
||||
|
} catch (Exception e) { |
||||
|
throw new RuntimeException("Failed to load security settings!", e); |
||||
|
} |
||||
|
} else { |
||||
|
securitySettings = new SecuritySettings(); |
||||
|
securitySettings.setPasswordPolicy(new UserPasswordPolicy()); |
||||
|
securitySettings.getPasswordPolicy().setMinimumLength(6); |
||||
|
} |
||||
|
return securitySettings; |
||||
|
} |
||||
|
|
||||
|
@CacheEvict(cacheNames = SECURITY_SETTINGS_CACHE, key = "'securitySettings'") |
||||
|
@Override |
||||
|
public SecuritySettings saveSecuritySettings(TenantId tenantId, SecuritySettings securitySettings) { |
||||
|
AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, "securitySettings"); |
||||
|
if (adminSettings == null) { |
||||
|
adminSettings = new AdminSettings(); |
||||
|
adminSettings.setKey("securitySettings"); |
||||
|
} |
||||
|
adminSettings.setJsonValue(objectMapper.valueToTree(securitySettings)); |
||||
|
AdminSettings savedAdminSettings = adminSettingsService.saveAdminSettings(tenantId, adminSettings); |
||||
|
try { |
||||
|
return objectMapper.treeToValue(savedAdminSettings.getJsonValue(), SecuritySettings.class); |
||||
|
} catch (Exception e) { |
||||
|
throw new RuntimeException("Failed to load security settings!", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String password) throws AuthenticationException { |
||||
|
|
||||
|
if (!encoder.matches(password, userCredentials.getPassword())) { |
||||
|
throw new BadCredentialsException("Authentication Failed. Username or Password not valid."); |
||||
|
} |
||||
|
|
||||
|
if (!userCredentials.isEnabled()) { |
||||
|
throw new DisabledException("User is not active"); |
||||
|
} |
||||
|
|
||||
|
SecuritySettings securitySettings = self.getSecuritySettings(tenantId); |
||||
|
if (isPositiveInteger(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) { |
||||
|
if ((userCredentials.getCreatedTime() |
||||
|
+ TimeUnit.DAYS.toMillis(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) |
||||
|
< System.currentTimeMillis()) { |
||||
|
userCredentials = userService.requestExpiredPasswordReset(tenantId, userCredentials.getId()); |
||||
|
throw new UserPasswordExpiredException("User password expired!", userCredentials.getResetToken()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void validatePassword(TenantId tenantId, String password) throws DataValidationException { |
||||
|
SecuritySettings securitySettings = self.getSecuritySettings(tenantId); |
||||
|
UserPasswordPolicy passwordPolicy = securitySettings.getPasswordPolicy(); |
||||
|
|
||||
|
List<Rule> passwordRules = new ArrayList<>(); |
||||
|
passwordRules.add(new LengthRule(passwordPolicy.getMinimumLength(), Integer.MAX_VALUE)); |
||||
|
if (isPositiveInteger(passwordPolicy.getMinimumUppercaseLetters())) { |
||||
|
passwordRules.add(new CharacterRule(EnglishCharacterData.UpperCase, passwordPolicy.getMinimumUppercaseLetters())); |
||||
|
} |
||||
|
if (isPositiveInteger(passwordPolicy.getMinimumLowercaseLetters())) { |
||||
|
passwordRules.add(new CharacterRule(EnglishCharacterData.LowerCase, passwordPolicy.getMinimumLowercaseLetters())); |
||||
|
} |
||||
|
if (isPositiveInteger(passwordPolicy.getMinimumDigits())) { |
||||
|
passwordRules.add(new CharacterRule(EnglishCharacterData.Digit, passwordPolicy.getMinimumDigits())); |
||||
|
} |
||||
|
if (isPositiveInteger(passwordPolicy.getMinimumSpecialCharacters())) { |
||||
|
passwordRules.add(new CharacterRule(EnglishCharacterData.Special, passwordPolicy.getMinimumSpecialCharacters())); |
||||
|
} |
||||
|
PasswordValidator validator = new PasswordValidator(passwordRules); |
||||
|
PasswordData passwordData = new PasswordData(password); |
||||
|
RuleResult result = validator.validate(passwordData); |
||||
|
if (!result.isValid()) { |
||||
|
String message = String.join("\n", validator.getMessages(result)); |
||||
|
throw new DataValidationException(message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static boolean isPositiveInteger(Integer val) { |
||||
|
return val != null && val.intValue() > 0; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.security.system; |
||||
|
|
||||
|
import org.springframework.security.core.AuthenticationException; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.security.UserCredentials; |
||||
|
import org.thingsboard.server.dao.exception.DataValidationException; |
||||
|
import org.thingsboard.server.service.security.model.SecuritySettings; |
||||
|
|
||||
|
public interface SystemSecurityService { |
||||
|
|
||||
|
SecuritySettings getSecuritySettings(TenantId tenantId); |
||||
|
|
||||
|
SecuritySettings saveSecuritySettings(TenantId tenantId, SecuritySettings securitySettings); |
||||
|
|
||||
|
void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String password) throws AuthenticationException; |
||||
|
|
||||
|
void validatePassword(TenantId tenantId, String password) throws DataValidationException; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
import './settings-card.scss'; |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function SecuritySettingsController(adminService, $mdExpansionPanel) { |
||||
|
|
||||
|
var vm = this; |
||||
|
vm.$mdExpansionPanel = $mdExpansionPanel; |
||||
|
|
||||
|
vm.save = save; |
||||
|
|
||||
|
loadSettings(); |
||||
|
|
||||
|
function loadSettings() { |
||||
|
adminService.getSecuritySettings().then(function success(securitySettings) { |
||||
|
vm.securitySettings = securitySettings; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function save() { |
||||
|
adminService.saveSecuritySettings(vm.securitySettings).then(function success(securitySettings) { |
||||
|
vm.securitySettings = securitySettings; |
||||
|
vm.settingsForm.$setPristine(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div tb-help="'securitySettings'" help-container-id="help-container"> |
||||
|
<md-card class="settings-card"> |
||||
|
<md-card-title> |
||||
|
<md-card-title-text layout="row"> |
||||
|
<span translate class="md-headline">admin.security-settings</span> |
||||
|
<span flex></span> |
||||
|
<div id="help-container"></div> |
||||
|
</md-card-title-text> |
||||
|
</md-card-title> |
||||
|
<md-progress-linear md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> |
||||
|
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> |
||||
|
<md-card-content> |
||||
|
<form name="vm.settingsForm" ng-submit="vm.save()" tb-confirm-on-exit confirm-form="vm.settingsForm"> |
||||
|
<fieldset ng-disabled="$root.loading"> |
||||
|
<md-expansion-panel-group md-component-id="securitySettingsPanelGroup" auto-expand="true" multiple> |
||||
|
<md-expansion-panel md-component-id="passwordPolicyPanel" id="passwordPolicyPanel"> |
||||
|
<md-expansion-panel-collapsed> |
||||
|
<div class="tb-panel-title" translate>admin.password-policy</div> |
||||
|
<md-expansion-panel-icon></md-expansion-panel-icon> |
||||
|
</md-expansion-panel-collapsed> |
||||
|
<md-expansion-panel-expanded> |
||||
|
<md-expansion-panel-header ng-click="vm.$mdExpansionPanel('passwordPolicyPanel').collapse()"> |
||||
|
<div class="tb-panel-title" translate>admin.password-policy</div> |
||||
|
<md-expansion-panel-icon></md-expansion-panel-icon> |
||||
|
</md-expansion-panel-header> |
||||
|
<md-expansion-panel-content> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>admin.minimum-password-length</label> |
||||
|
<input type="number" |
||||
|
step="1" |
||||
|
min="5" |
||||
|
max="50" |
||||
|
required |
||||
|
name="minimumPasswordLength" |
||||
|
ng-model="vm.securitySettings.passwordPolicy.minimumLength"> |
||||
|
<div ng-messages="vm.settingsForm.minimumPasswordLength.$error"> |
||||
|
<div translate ng-message="required">admin.minimum-password-length-required</div> |
||||
|
<div translate ng-message="min">admin.minimum-password-length-range</div> |
||||
|
<div translate ng-message="max">admin.minimum-password-length-range</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>admin.minimum-uppercase-letters</label> |
||||
|
<input type="number" |
||||
|
step="1" |
||||
|
min="0" |
||||
|
name="minimumUppercaseLetters" |
||||
|
ng-model="vm.securitySettings.passwordPolicy.minimumUppercaseLetters"> |
||||
|
<div ng-messages="vm.settingsForm.minimumUppercaseLetters.$error"> |
||||
|
<div translate ng-message="min">admin.minimum-uppercase-letters-range</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>admin.minimum-lowercase-letters</label> |
||||
|
<input type="number" |
||||
|
step="1" |
||||
|
min="0" |
||||
|
name="minimumLowercaseLetters" |
||||
|
ng-model="vm.securitySettings.passwordPolicy.minimumLowercaseLetters"> |
||||
|
<div ng-messages="vm.settingsForm.minimumLowercaseLetters.$error"> |
||||
|
<div translate ng-message="min">admin.minimum-lowercase-letters-range</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>admin.minimum-digits</label> |
||||
|
<input type="number" |
||||
|
step="1" |
||||
|
min="0" |
||||
|
name="minimumDigits" |
||||
|
ng-model="vm.securitySettings.passwordPolicy.minimumDigits"> |
||||
|
<div ng-messages="vm.settingsForm.minimumDigits.$error"> |
||||
|
<div translate ng-message="min">admin.minimum-digits-range</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>admin.minimum-special-characters</label> |
||||
|
<input type="number" |
||||
|
step="1" |
||||
|
min="0" |
||||
|
name="minimumSpecialCharacters" |
||||
|
ng-model="vm.securitySettings.passwordPolicy.minimumSpecialCharacters"> |
||||
|
<div ng-messages="vm.settingsForm.minimumSpecialCharacters.$error"> |
||||
|
<div translate ng-message="min">admin.minimum-special-characters-range</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>admin.password-expiration-period-days</label> |
||||
|
<input type="number" |
||||
|
step="1" |
||||
|
min="0" |
||||
|
name="passwordExpirationPeriodDays" |
||||
|
ng-model="vm.securitySettings.passwordPolicy.passwordExpirationPeriodDays"> |
||||
|
<div ng-messages="vm.settingsForm.passwordExpirationPeriodDays.$error"> |
||||
|
<div translate ng-message="min">admin.password-expiration-period-days-range</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</md-expansion-panel-content> |
||||
|
</md-expansion-panel-expanded> |
||||
|
</md-expansion-panel> |
||||
|
</md-expansion-panel-group> |
||||
|
<div layout="row" layout-align="end center" width="100%" layout-wrap> |
||||
|
<md-button ng-disabled="$root.loading || vm.settingsForm.$invalid || !vm.settingsForm.$dirty" type="submit" class="md-raised md-primary">{{'action.save' | translate}}</md-button> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</form> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</div> |
||||
Loading…
Reference in new issue