KEYCLOAK-5459
This commit is contained in:
parent
913c94dbd1
commit
6b8ead6c4b
6 changed files with 245 additions and 236 deletions
|
@ -64,268 +64,277 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
*/
|
*/
|
||||||
public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(BlacklistPasswordPolicyProviderFactory.class);
|
private static final Logger LOG = Logger.getLogger(BlacklistPasswordPolicyProviderFactory.class);
|
||||||
|
|
||||||
public static final String ID = "passwordBlacklist";
|
public static final String ID = "passwordBlacklist";
|
||||||
|
|
||||||
public static final String SYSTEM_PROPERTY = "keycloak.password.blacklists.path";
|
public static final String SYSTEM_PROPERTY = "keycloak.password.blacklists.path";
|
||||||
|
|
||||||
public static final String BLACKLISTS_PATH_PROPERTY = "blacklistsPath";
|
public static final String BLACKLISTS_PATH_PROPERTY = "blacklistsPath";
|
||||||
|
|
||||||
public static final String JBOSS_SERVER_DATA_DIR = "jboss.server.data.dir";
|
public static final String JBOSS_SERVER_DATA_DIR = "jboss.server.data.dir";
|
||||||
|
|
||||||
public static final String PASSWORD_BLACKLISTS_FOLDER = "password-blacklists/";
|
public static final String PASSWORD_BLACKLISTS_FOLDER = "password-blacklists/";
|
||||||
|
|
||||||
private ConcurrentMap<String, FileBasedPasswordBlacklist> blacklistRegistry = new ConcurrentHashMap<>();
|
private ConcurrentMap<String, FileBasedPasswordBlacklist> blacklistRegistry = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private Path blacklistsBasePath;
|
private volatile Path blacklistsBasePath;
|
||||||
|
|
||||||
@Override
|
private Config.Scope config;
|
||||||
public PasswordPolicyProvider create(KeycloakSession session) {
|
|
||||||
return new BlacklistPasswordPolicyProvider(session.getContext(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
this.blacklistsBasePath = FileBasedPasswordBlacklist.detectBlacklistsBasePath(config);
|
if (this.blacklistsBasePath == null) {
|
||||||
}
|
synchronized (this) {
|
||||||
|
if (this.blacklistsBasePath == null) {
|
||||||
@Override
|
this.blacklistsBasePath = FileBasedPasswordBlacklist.detectBlacklistsBasePath(config);
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
return new BlacklistPasswordPolicyProvider(session.getContext(), this);
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayName() {
|
|
||||||
return "Password Blacklist";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getConfigType() {
|
|
||||||
return PasswordPolicyProvider.STRING_CONFIG_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDefaultConfigValue() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isMultiplSupported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves and potentially registers a {@link PasswordBlacklist} for the given {@code blacklistName}.
|
|
||||||
*
|
|
||||||
* @param blacklistName
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public PasswordBlacklist resolvePasswordBlacklist(String blacklistName) {
|
|
||||||
|
|
||||||
Objects.requireNonNull(blacklistName, "blacklistName");
|
|
||||||
|
|
||||||
String cleanedBlacklistName = blacklistName.trim();
|
|
||||||
if (cleanedBlacklistName.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Password blacklist name must not be empty!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return blacklistRegistry.computeIfAbsent(cleanedBlacklistName, (name) -> {
|
@Override
|
||||||
FileBasedPasswordBlacklist pbl = new FileBasedPasswordBlacklist(this.blacklistsBasePath, name);
|
public void init(Config.Scope config) {
|
||||||
pbl.lazyInit();
|
this.config = config;
|
||||||
return pbl;
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* A {@link PasswordBlacklist} describes a list of too easy to guess
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
* or potentially leaked passwords that users should not be able to use.
|
}
|
||||||
*/
|
|
||||||
public interface PasswordBlacklist {
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Password Blacklist";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.STRING_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the logical name of the {@link PasswordBlacklist}
|
* Resolves and potentially registers a {@link PasswordBlacklist} for the given {@code blacklistName}.
|
||||||
*/
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a given {@code password} is contained in this {@link PasswordBlacklist}.
|
|
||||||
*
|
*
|
||||||
* @param password
|
* @param blacklistName
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
boolean contains(String password);
|
public PasswordBlacklist resolvePasswordBlacklist(String blacklistName) {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Objects.requireNonNull(blacklistName, "blacklistName");
|
||||||
* A {@link FileBasedPasswordBlacklist} uses password-blacklist files as
|
|
||||||
* to construct a {@link PasswordBlacklist}.
|
|
||||||
* <p>
|
|
||||||
* This implementation uses a dynamically sized {@link BloomFilter}
|
|
||||||
* to provide a false positive probability of 1%.
|
|
||||||
*
|
|
||||||
* @see BloomFilter
|
|
||||||
*/
|
|
||||||
public static class FileBasedPasswordBlacklist implements PasswordBlacklist {
|
|
||||||
|
|
||||||
private static final double FALSE_POSITIVE_PROBABILITY = 0.01;
|
String cleanedBlacklistName = blacklistName.trim();
|
||||||
|
if (cleanedBlacklistName.isEmpty()) {
|
||||||
private static final int BUFFER_SIZE_IN_BYTES = 512 * 1024;
|
throw new IllegalArgumentException("Password blacklist name must not be empty!");
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the blacklist filename.
|
|
||||||
*/
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The concrete path to the password-blacklist file.
|
|
||||||
*/
|
|
||||||
private final Path path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialized lazily via {@link #lazyInit()}
|
|
||||||
*/
|
|
||||||
private BloomFilter<String> blacklist;
|
|
||||||
|
|
||||||
public FileBasedPasswordBlacklist(Path blacklistBasePath, String name) {
|
|
||||||
|
|
||||||
this.name = name;
|
|
||||||
this.path = blacklistBasePath.resolve(name);
|
|
||||||
|
|
||||||
|
|
||||||
if (name.contains("/")) {
|
|
||||||
// disallow '/' to avoid accidental filesystem traversal
|
|
||||||
throw new IllegalArgumentException("" + name + " must not contain slashes!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Files.exists(this.path)) {
|
|
||||||
throw new IllegalArgumentException("Password blacklist " + name + " not found!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean contains(String password) {
|
|
||||||
return blacklist != null && blacklist.mightContain(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lazyInit() {
|
|
||||||
|
|
||||||
if (blacklist != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.blacklist = load();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the referenced blacklist into a {@link BloomFilter}.
|
|
||||||
*
|
|
||||||
* @return the {@link BloomFilter} backing a password blacklist
|
|
||||||
*/
|
|
||||||
private BloomFilter<String> load() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
LOG.infof("Loading blacklist with name %s from %s - start", name, path);
|
|
||||||
|
|
||||||
long passwordCount = getPasswordCount();
|
|
||||||
|
|
||||||
BloomFilter<String> filter = BloomFilter.create(
|
|
||||||
Funnels.stringFunnel(StandardCharsets.UTF_8),
|
|
||||||
passwordCount,
|
|
||||||
FALSE_POSITIVE_PROBABILITY);
|
|
||||||
|
|
||||||
try (BufferedReader br = newReader(path)) {
|
|
||||||
br.lines().forEach(filter::put);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.infof("Loading blacklist with name %s from %s - end", name, path);
|
return blacklistRegistry.computeIfAbsent(cleanedBlacklistName, (name) -> {
|
||||||
|
FileBasedPasswordBlacklist pbl = new FileBasedPasswordBlacklist(this.blacklistsBasePath, name);
|
||||||
return filter;
|
pbl.lazyInit();
|
||||||
} catch (IOException e) {
|
return pbl;
|
||||||
throw new RuntimeException("Could not load password blacklist from path: " + path, e);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines password blacklist size to correctly size the {@link BloomFilter} backing this blacklist.
|
* A {@link PasswordBlacklist} describes a list of too easy to guess
|
||||||
*
|
* or potentially leaked passwords that users should not be able to use.
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
private long getPasswordCount() throws IOException {
|
public interface PasswordBlacklist {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the logical name of the {@link PasswordBlacklist}
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a given {@code password} is contained in this {@link PasswordBlacklist}.
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean contains(String password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link FileBasedPasswordBlacklist} uses password-blacklist files as
|
||||||
|
* to construct a {@link PasswordBlacklist}.
|
||||||
|
* <p>
|
||||||
|
* This implementation uses a dynamically sized {@link BloomFilter}
|
||||||
|
* to provide a false positive probability of 1%.
|
||||||
|
*
|
||||||
|
* @see BloomFilter
|
||||||
|
*/
|
||||||
|
public static class FileBasedPasswordBlacklist implements PasswordBlacklist {
|
||||||
|
|
||||||
|
private static final double FALSE_POSITIVE_PROBABILITY = 0.01;
|
||||||
|
|
||||||
|
private static final int BUFFER_SIZE_IN_BYTES = 512 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the blacklist filename.
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The concrete path to the password-blacklist file.
|
||||||
|
*/
|
||||||
|
private final Path path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialized lazily via {@link #lazyInit()}
|
||||||
|
*/
|
||||||
|
private BloomFilter<String> blacklist;
|
||||||
|
|
||||||
|
public FileBasedPasswordBlacklist(Path blacklistBasePath, String name) {
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.path = blacklistBasePath.resolve(name);
|
||||||
|
|
||||||
|
|
||||||
|
if (name.contains("/")) {
|
||||||
|
// disallow '/' to avoid accidental filesystem traversal
|
||||||
|
throw new IllegalArgumentException("" + name + " must not contain slashes!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(this.path)) {
|
||||||
|
throw new IllegalArgumentException("Password blacklist " + name + " not found!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(String password) {
|
||||||
|
return blacklist != null && blacklist.mightContain(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lazyInit() {
|
||||||
|
|
||||||
|
if (blacklist != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.blacklist = load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the referenced blacklist into a {@link BloomFilter}.
|
||||||
|
*
|
||||||
|
* @return the {@link BloomFilter} backing a password blacklist
|
||||||
|
*/
|
||||||
|
private BloomFilter<String> load() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
LOG.infof("Loading blacklist with name %s from %s - start", name, path);
|
||||||
|
|
||||||
|
long passwordCount = getPasswordCount();
|
||||||
|
|
||||||
|
BloomFilter<String> filter = BloomFilter.create(
|
||||||
|
Funnels.stringFunnel(StandardCharsets.UTF_8),
|
||||||
|
passwordCount,
|
||||||
|
FALSE_POSITIVE_PROBABILITY);
|
||||||
|
|
||||||
|
try (BufferedReader br = newReader(path)) {
|
||||||
|
br.lines().forEach(filter::put);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.infof("Loading blacklist with name %s from %s - end", name, path);
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Could not load password blacklist from path: " + path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines password blacklist size to correctly size the {@link BloomFilter} backing this blacklist.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private long getPasswordCount() throws IOException {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO find a more efficient way to determine the password count,
|
* TODO find a more efficient way to determine the password count,
|
||||||
* e.g. require a header-line in the password-blacklist file
|
* e.g. require a header-line in the password-blacklist file
|
||||||
*/
|
*/
|
||||||
try (BufferedReader br = newReader(path)) {
|
try (BufferedReader br = newReader(path)) {
|
||||||
return br.lines().count();
|
return br.lines().count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BufferedReader newReader(Path path) throws IOException {
|
private static BufferedReader newReader(Path path) throws IOException {
|
||||||
return new BufferedReader(Files.newBufferedReader(path), BUFFER_SIZE_IN_BYTES);
|
return new BufferedReader(Files.newBufferedReader(path), BUFFER_SIZE_IN_BYTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discovers password blacklists location.
|
* Discovers password blacklists location.
|
||||||
* <p>
|
* <p>
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>
|
* <li>
|
||||||
* system property {@code keycloak.password.blacklists.path} if present
|
* system property {@code keycloak.password.blacklists.path} if present
|
||||||
* </li>
|
* </li>
|
||||||
* <li>SPI config property {@code blacklistsPath}</li>
|
* <li>SPI config property {@code blacklistsPath}</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* and fallback to the {@code /data/password-blacklists} folder of the currently
|
* and fallback to the {@code /data/password-blacklists} folder of the currently
|
||||||
* running wildfly instance.
|
* running wildfly instance.
|
||||||
*
|
*
|
||||||
* @param config
|
* @param config
|
||||||
* @return the detected blacklist path
|
* @return the detected blacklist path
|
||||||
* @throws IllegalStateException if no blacklist folder could be detected
|
* @throws IllegalStateException if no blacklist folder could be detected
|
||||||
*/
|
*/
|
||||||
private static Path detectBlacklistsBasePath(Config.Scope config) {
|
private static Path detectBlacklistsBasePath(Config.Scope config) {
|
||||||
|
|
||||||
String pathFromSysProperty = System.getProperty(SYSTEM_PROPERTY);
|
String pathFromSysProperty = System.getProperty(SYSTEM_PROPERTY);
|
||||||
if (pathFromSysProperty != null) {
|
if (pathFromSysProperty != null) {
|
||||||
return ensureExists(Paths.get(pathFromSysProperty));
|
return ensureExists(Paths.get(pathFromSysProperty));
|
||||||
}
|
}
|
||||||
|
|
||||||
String pathFromSpiConfig = config.get(BLACKLISTS_PATH_PROPERTY);
|
String pathFromSpiConfig = config.get(BLACKLISTS_PATH_PROPERTY);
|
||||||
if (pathFromSpiConfig != null) {
|
if (pathFromSpiConfig != null) {
|
||||||
return ensureExists(Paths.get(pathFromSpiConfig));
|
return ensureExists(Paths.get(pathFromSpiConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
String pathFromJbossDataPath = System.getProperty(JBOSS_SERVER_DATA_DIR) + "/" + PASSWORD_BLACKLISTS_FOLDER;
|
String pathFromJbossDataPath = System.getProperty(JBOSS_SERVER_DATA_DIR) + "/" + PASSWORD_BLACKLISTS_FOLDER;
|
||||||
if (!Files.exists(Paths.get(pathFromJbossDataPath))) {
|
if (!Files.exists(Paths.get(pathFromJbossDataPath))) {
|
||||||
if (!Paths.get(pathFromJbossDataPath).toFile().mkdirs()) {
|
if (!Paths.get(pathFromJbossDataPath).toFile().mkdirs()) {
|
||||||
LOG.errorf("Could not create folder for password blacklists: %s", pathFromJbossDataPath);
|
LOG.errorf("Could not create folder for password blacklists: %s", pathFromJbossDataPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ensureExists(Paths.get(pathFromJbossDataPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path ensureExists(Path path) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(path, "path");
|
||||||
|
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("Password blacklists location does not exist: " + path);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return ensureExists(Paths.get(pathFromJbossDataPath));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path ensureExists(Path path) {
|
|
||||||
|
|
||||||
Objects.requireNonNull(path, "path");
|
|
||||||
|
|
||||||
if (Files.exists(path)) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("Password blacklists location does not exist: " + path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/service-account-roles">{{:: 'service-account-roles' | translate}}</a>
|
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/service-account-roles">{{:: 'service-account-roles' | translate}}</a>
|
||||||
<kc-tooltip>{{:: 'service-account-roles.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'service-account-roles.tooltip' | translate}}</kc-tooltip>
|
||||||
</li>
|
</li>
|
||||||
<li ng-class="{active: path[4] == 'permissions'}" data-ng-show="client.access.manage && access.manageAuthorization">
|
<li ng-class="{active: path[4] == 'permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && client.access.manage && access.manageAuthorization">
|
||||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
|
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
|
||||||
<kc-tooltip>{{:: 'manage-permissions-client.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'manage-permissions-client.tooltip' | translate}}</kc-tooltip>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<li ng-class="{active: path[4] == 'attributes'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/attributes">{{:: 'attributes' | translate}}</a></li>
|
<li ng-class="{active: path[4] == 'attributes'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/attributes">{{:: 'attributes' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/role-mappings">{{:: 'role-mappings' | translate}}</a></li>
|
<li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/role-mappings">{{:: 'role-mappings' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[4] == 'members'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/members">{{:: 'members' | translate}}</a></li>
|
<li ng-class="{active: path[4] == 'members'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/members">{{:: 'members' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[4] == 'permissions'}" data-ng-show="group.access.manage && access.manageAuthorization">
|
<li ng-class="{active: path[4] == 'permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && group.access.manage && access.manageAuthorization">
|
||||||
<a href="#/realms/{{realm.realm}}/groups/{{group.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
|
<a href="#/realms/{{realm.realm}}/groups/{{group.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
|
||||||
<kc-tooltip>{{:: 'manage-permissions-group.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'manage-permissions-group.tooltip' | translate}}</kc-tooltip>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -12,6 +12,6 @@
|
||||||
<li ng-class="{active: !path[6] && path.length > 5}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{:: 'settings' | translate}}</a></li>
|
<li ng-class="{active: !path[6] && path.length > 5}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{:: 'settings' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[4] == 'mappers'}"><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">{{:: 'mappers' | translate}}</a></li>
|
<li ng-class="{active: path[4] == 'mappers'}"><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">{{:: 'mappers' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[6] == 'export'}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider && identityProvider.providerId == 'saml'">{{:: 'export' | translate}}</a></li>
|
<li ng-class="{active: path[6] == 'export'}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider && identityProvider.providerId == 'saml'">{{:: 'export' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[6] == 'permissions'}" data-ng-show="!newIdentityProvider && access.manageAuthorization"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/permissions">{{:: 'authz-permissions' | translate}}</a></li>
|
<li ng-class="{active: path[6] == 'permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && !newIdentityProvider && access.manageAuthorization"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/permissions">{{:: 'authz-permissions' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
<ul class="nav nav-tabs" data-ng-show="!create">
|
<ul class="nav nav-tabs" data-ng-show="!create">
|
||||||
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/roles/{{role.id}}">{{:: 'details' | translate}}</a></li>
|
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/roles/{{role.id}}">{{:: 'details' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[4] == 'permissions'}" data-ng-show="access.manageRealm && access.manageAuthorization">
|
<li ng-class="{active: path[4] == 'permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && access.manageRealm && access.manageAuthorization">
|
||||||
<a href="#/realms/{{realm.realm}}/roles/{{role.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
|
<a href="#/realms/{{realm.realm}}/roles/{{role.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
|
||||||
<kc-tooltip>{{:: 'manage-permissions-role.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'manage-permissions-role.tooltip' | translate}}</kc-tooltip>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li ng-class="{active: path[2] == 'users'}"><a href="#/realms/{{realm.realm}}/users">{{:: 'lookup' | translate}}</a></li>
|
<li ng-class="{active: path[2] == 'users'}"><a href="#/realms/{{realm.realm}}/users">{{:: 'lookup' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[2] == 'users-permissions'}" data-ng-show="access.manageUsers && access.manageAuthorization">
|
<li ng-class="{active: path[2] == 'users-permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && access.manageUsers && access.manageAuthorization">
|
||||||
<a href="#/realms/{{realm.realm}}/users-permissions">{{:: 'authz-permissions' | translate}}</a>
|
<a href="#/realms/{{realm.realm}}/users-permissions">{{:: 'authz-permissions' | translate}}</a>
|
||||||
<kc-tooltip>{{:: 'manage-permissions-users.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'manage-permissions-users.tooltip' | translate}}</kc-tooltip>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in a new issue