Merge remote-tracking branch 'upstream/master' into kcinit
This commit is contained in:
commit
f000cedcbb
391 changed files with 6319 additions and 6361 deletions
|
@ -95,7 +95,9 @@ class PathMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
|
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
|
||||||
matchingAnyPath = entry;
|
if (matchingAnyPath == null || matchingAnyPath.getPath().length() < matchingUri.length()) {
|
||||||
|
matchingAnyPath = entry;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int suffixIndex = expectedUri.indexOf(WILDCARD + ".");
|
int suffixIndex = expectedUri.indexOf(WILDCARD + ".");
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ public class AuthzClient {
|
||||||
* @return a {@link ProtectionResource}
|
* @return a {@link ProtectionResource}
|
||||||
*/
|
*/
|
||||||
public ProtectionResource protection() {
|
public ProtectionResource protection() {
|
||||||
return new ProtectionResource(this.http, this.serverConfiguration, createPatSupplier());
|
return new ProtectionResource(this.http, this.serverConfiguration, configuration, createPatSupplier());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,7 +107,7 @@ public class AuthzClient {
|
||||||
* @return a {@link ProtectionResource}
|
* @return a {@link ProtectionResource}
|
||||||
*/
|
*/
|
||||||
public ProtectionResource protection(final String accessToken) {
|
public ProtectionResource protection(final String accessToken) {
|
||||||
return new ProtectionResource(this.http, this.serverConfiguration, new TokenCallable(http, configuration, serverConfiguration) {
|
return new ProtectionResource(this.http, this.serverConfiguration, configuration, new TokenCallable(http, configuration, serverConfiguration) {
|
||||||
@Override
|
@Override
|
||||||
public String call() {
|
public String call() {
|
||||||
return accessToken;
|
return accessToken;
|
||||||
|
@ -128,7 +128,7 @@ public class AuthzClient {
|
||||||
* @return a {@link ProtectionResource}
|
* @return a {@link ProtectionResource}
|
||||||
*/
|
*/
|
||||||
public ProtectionResource protection(String userName, String password) {
|
public ProtectionResource protection(String userName, String password) {
|
||||||
return new ProtectionResource(this.http, this.serverConfiguration, createPatSupplier(userName, password));
|
return new ProtectionResource(this.http, this.serverConfiguration, configuration, createPatSupplier(userName, password));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.keycloak.authorization.client.Configuration;
|
||||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||||
import org.keycloak.authorization.client.util.Http;
|
import org.keycloak.authorization.client.util.Http;
|
||||||
|
@ -38,11 +39,13 @@ public class ProtectedResource {
|
||||||
|
|
||||||
private final Http http;
|
private final Http http;
|
||||||
private ServerConfiguration serverConfiguration;
|
private ServerConfiguration serverConfiguration;
|
||||||
|
private final Configuration configuration;
|
||||||
private final TokenCallable pat;
|
private final TokenCallable pat;
|
||||||
|
|
||||||
ProtectedResource(Http http, ServerConfiguration serverConfiguration, TokenCallable pat) {
|
ProtectedResource(Http http, ServerConfiguration serverConfiguration, Configuration configuration, TokenCallable pat) {
|
||||||
this.http = http;
|
this.http = http;
|
||||||
this.serverConfiguration = serverConfiguration;
|
this.serverConfiguration = serverConfiguration;
|
||||||
|
this.configuration = configuration;
|
||||||
this.pat = pat;
|
this.pat = pat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,13 +122,30 @@ public class ProtectedResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query the server for a resource given its <code>name</code>.
|
* Query the server for a resource given its <code>name</code> where the owner is the resource server itself.
|
||||||
*
|
*
|
||||||
* @param id the resource name
|
* @param id the resource name
|
||||||
* @return a {@link ResourceRepresentation}
|
* @return a {@link ResourceRepresentation}
|
||||||
*/
|
*/
|
||||||
public ResourceRepresentation findByName(String name) {
|
public ResourceRepresentation findByName(String name) {
|
||||||
String[] representations = find(null, name, null, null, null, null, null, null);
|
String[] representations = find(null, name, null, configuration.getResource(), null, null, null, null);
|
||||||
|
|
||||||
|
if (representations.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return findById(representations[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the server for a resource given its <code>name</code> and a given <code>ownerId</code>.
|
||||||
|
*
|
||||||
|
* @param name the resource name
|
||||||
|
* @param ownerId the owner id
|
||||||
|
* @return a {@link ResourceRepresentation}
|
||||||
|
*/
|
||||||
|
public ResourceRepresentation findByName(String name, String ownerId) {
|
||||||
|
String[] representations = find(null, name, null, ownerId, null, null, null, null);
|
||||||
|
|
||||||
if (representations.length == 0) {
|
if (representations.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authorization.client.resource;
|
package org.keycloak.authorization.client.resource;
|
||||||
|
|
||||||
|
import org.keycloak.authorization.client.Configuration;
|
||||||
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
import org.keycloak.authorization.client.representation.ServerConfiguration;
|
||||||
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
||||||
import org.keycloak.authorization.client.util.Http;
|
import org.keycloak.authorization.client.util.Http;
|
||||||
|
@ -31,15 +32,17 @@ public class ProtectionResource {
|
||||||
|
|
||||||
private final TokenCallable pat;
|
private final TokenCallable pat;
|
||||||
private final Http http;
|
private final Http http;
|
||||||
|
private final Configuration configuration;
|
||||||
private ServerConfiguration serverConfiguration;
|
private ServerConfiguration serverConfiguration;
|
||||||
|
|
||||||
public ProtectionResource(Http http, ServerConfiguration serverConfiguration, TokenCallable pat) {
|
public ProtectionResource(Http http, ServerConfiguration serverConfiguration, Configuration configuration, TokenCallable pat) {
|
||||||
if (pat == null) {
|
if (pat == null) {
|
||||||
throw new RuntimeException("No access token was provided when creating client for Protection API.");
|
throw new RuntimeException("No access token was provided when creating client for Protection API.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.http = http;
|
this.http = http;
|
||||||
this.serverConfiguration = serverConfiguration;
|
this.serverConfiguration = serverConfiguration;
|
||||||
|
this.configuration = configuration;
|
||||||
this.pat = pat;
|
this.pat = pat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +52,7 @@ public class ProtectionResource {
|
||||||
* @return a {@link ProtectedResource}
|
* @return a {@link ProtectedResource}
|
||||||
*/
|
*/
|
||||||
public ProtectedResource resource() {
|
public ProtectedResource resource() {
|
||||||
return new ProtectedResource(http, serverConfiguration, pat);
|
return new ProtectedResource(http, serverConfiguration, configuration, pat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -44,4 +44,12 @@ public class HttpResponseException extends RuntimeException {
|
||||||
public byte[] getBytes() {
|
public byte[] getBytes() {
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (bytes != null) {
|
||||||
|
return new StringBuilder(super.toString()).append(" / Response from server: ").append(new String(bytes)).toString();
|
||||||
|
}
|
||||||
|
return super.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ public final class Throwables {
|
||||||
*/
|
*/
|
||||||
public static RuntimeException handleWrapException(String message, Throwable cause) {
|
public static RuntimeException handleWrapException(String message, Throwable cause) {
|
||||||
if (cause instanceof HttpResponseException) {
|
if (cause instanceof HttpResponseException) {
|
||||||
throw handleAndWrapHttpResponseException(message, HttpResponseException.class.cast(cause));
|
throw handleAndWrapHttpResponseException(HttpResponseException.class.cast(cause));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RuntimeException(message, cause);
|
return new RuntimeException(message, cause);
|
||||||
|
@ -91,19 +91,11 @@ public final class Throwables {
|
||||||
throw new RuntimeException(message, cause);
|
throw new RuntimeException(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RuntimeException handleAndWrapHttpResponseException(String message, HttpResponseException exception) {
|
private static RuntimeException handleAndWrapHttpResponseException(HttpResponseException exception) {
|
||||||
HttpResponseException hre = HttpResponseException.class.cast(exception);
|
|
||||||
StringBuilder detail = new StringBuilder(message);
|
|
||||||
byte[] bytes = hre.getBytes();
|
|
||||||
|
|
||||||
if (bytes != null) {
|
|
||||||
detail.append(". Server message: ").append(new String(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (403 == exception.getStatusCode()) {
|
if (403 == exception.getStatusCode()) {
|
||||||
throw new AuthorizationDeniedException(detail.toString(), exception);
|
throw new AuthorizationDeniedException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RuntimeException(detail.toString(), exception);
|
return new RuntimeException(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,10 @@ public class IDToken extends JsonWebToken {
|
||||||
public static final String CLAIMS_LOCALES = "claims_locales";
|
public static final String CLAIMS_LOCALES = "claims_locales";
|
||||||
public static final String ACR = "acr";
|
public static final String ACR = "acr";
|
||||||
|
|
||||||
|
// Financial API - Part 2: Read and Write API Security Profile
|
||||||
|
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||||
|
public static final String S_HASH = "s_hash";
|
||||||
|
|
||||||
// NOTE!!! WE used to use @JsonUnwrapped on a UserClaimSet object. This screws up otherClaims and the won't work
|
// NOTE!!! WE used to use @JsonUnwrapped on a UserClaimSet object. This screws up otherClaims and the won't work
|
||||||
// anymore. So don't have any @JsonUnwrapped!
|
// anymore. So don't have any @JsonUnwrapped!
|
||||||
@JsonProperty(NONCE)
|
@JsonProperty(NONCE)
|
||||||
|
@ -131,6 +135,11 @@ public class IDToken extends JsonWebToken {
|
||||||
@JsonProperty(ACR)
|
@JsonProperty(ACR)
|
||||||
protected String acr;
|
protected String acr;
|
||||||
|
|
||||||
|
// Financial API - Part 2: Read and Write API Security Profile
|
||||||
|
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||||
|
@JsonProperty(S_HASH)
|
||||||
|
protected String stateHash;
|
||||||
|
|
||||||
public String getNonce() {
|
public String getNonce() {
|
||||||
return nonce;
|
return nonce;
|
||||||
}
|
}
|
||||||
|
@ -338,4 +347,14 @@ public class IDToken extends JsonWebToken {
|
||||||
public void setAcr(String acr) {
|
public void setAcr(String acr) {
|
||||||
this.acr = acr;
|
this.acr = acr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Financial API - Part 2: Read and Write API Security Profile
|
||||||
|
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||||
|
public String getStateHash() {
|
||||||
|
return stateHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStateHash(String stateHash) {
|
||||||
|
this.stateHash = stateHash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
<file>
|
<file>
|
||||||
<source>src/index.html</source>
|
<source>src/index.html</source>
|
||||||
<outputDirectory></outputDirectory>
|
<outputDirectory></outputDirectory>
|
||||||
|
<filtered>true</filtered>
|
||||||
</file>
|
</file>
|
||||||
</files>
|
</files>
|
||||||
|
|
||||||
|
|
|
@ -29,13 +29,9 @@
|
||||||
<name>Keycloak Docs Distribution</name>
|
<name>Keycloak Docs Distribution</name>
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<dependencies>
|
<properties>
|
||||||
<dependency>
|
<javadoc.branding>${product.name.full} ${product.version}</javadoc.branding>
|
||||||
<groupId>org.keycloak</groupId>
|
</properties>
|
||||||
<artifactId>keycloak-dependencies-server-all</artifactId>
|
|
||||||
<type>pom</type>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<finalName>keycloak-api-docs-${project.version}</finalName>
|
<finalName>keycloak-api-docs-${project.version}</finalName>
|
||||||
|
@ -45,12 +41,9 @@
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<minmemory>128m</minmemory>
|
<minmemory>128m</minmemory>
|
||||||
<maxmemory>1024m</maxmemory>
|
<maxmemory>2400m</maxmemory>
|
||||||
<dependencySourceIncludes>
|
<encoding>UTF-8</encoding>
|
||||||
<dependencySourceInclude>org.keycloak:*</dependencySourceInclude>
|
|
||||||
</dependencySourceIncludes>
|
|
||||||
<includeDependencySources>true</includeDependencySources>
|
<includeDependencySources>true</includeDependencySources>
|
||||||
<includeTransitiveDependencySources>true</includeTransitiveDependencySources>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
@ -75,12 +68,6 @@
|
||||||
<descriptors>
|
<descriptors>
|
||||||
<descriptor>assembly.xml</descriptor>
|
<descriptor>assembly.xml</descriptor>
|
||||||
</descriptors>
|
</descriptors>
|
||||||
<outputDirectory>
|
|
||||||
target
|
|
||||||
</outputDirectory>
|
|
||||||
<workDirectory>
|
|
||||||
target/assembly/work
|
|
||||||
</workDirectory>
|
|
||||||
<appendAssemblyId>false</appendAssemblyId>
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
@ -89,7 +76,6 @@
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
<id>community</id>
|
<id>community</id>
|
||||||
|
@ -98,8 +84,30 @@
|
||||||
<name>!product</name>
|
<name>!product</name>
|
||||||
</property>
|
</property>
|
||||||
</activation>
|
</activation>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-dependencies-server-all</artifactId>
|
||||||
|
<type>pom</type>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>aggregate-javadoc</id>
|
||||||
|
<configuration>
|
||||||
|
<includeTransitiveDependencySources>true</includeTransitiveDependencySources>
|
||||||
|
<dependencySourceIncludes>
|
||||||
|
<dependencySourceInclude>org.keycloak:*</dependencySourceInclude>
|
||||||
|
</dependencySourceIncludes>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-deploy-plugin</artifactId>
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
@ -110,6 +118,76 @@
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>product</id>
|
||||||
|
<activation>
|
||||||
|
<property>
|
||||||
|
<name>product</name>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
<!-- Make sure to keep this list in sync with <dependencySourceIncludes> -->
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-server-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-saml-core-public</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-saml-adapter-api-public</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>aggregate-javadoc</id>
|
||||||
|
<configuration>
|
||||||
|
<windowtitle>${javadoc.branding} public API</windowtitle>
|
||||||
|
<doctitle>${javadoc.branding} public API</doctitle>
|
||||||
|
<header>${javadoc.branding}</header>
|
||||||
|
<footer>${javadoc.branding}</footer>
|
||||||
|
<includeTransitiveDependencySources>false</includeTransitiveDependencySources>
|
||||||
|
<!-- Make sure to keep this list in sync with <dependencies> -->
|
||||||
|
<dependencySourceIncludes>
|
||||||
|
<include>org.keycloak:keycloak-server-spi</include>
|
||||||
|
<include>org.keycloak:keycloak-common</include>
|
||||||
|
<include>org.keycloak:keycloak-core</include>
|
||||||
|
<include>org.keycloak:keycloak-saml-core-public</include>
|
||||||
|
<include>org.keycloak:keycloak-adapter-spi</include>
|
||||||
|
<include>org.keycloak:keycloak-adapter-core</include>
|
||||||
|
<include>org.keycloak:keycloak-saml-adapter-api-public</include>
|
||||||
|
</dependencySourceIncludes>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>Keyloak API Documentation</h1>
|
<h1>${product.name.full} API Documentation</h1>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Admin REST API</td>
|
<td>Admin REST API</td>
|
||||||
|
@ -35,4 +35,4 @@
|
||||||
<td colspan="3"><a href="javadocs/index.html">HTML</a></td>
|
<td colspan="3"><a href="javadocs/index.html">HTML</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
# About the Example Application
|
|
||||||
|
|
||||||
This is a simple application to get you started with Keycloak Authorization Services.
|
|
||||||
|
|
||||||
It provides a single page application which is protected by a policy enforcer that decides whether an user can access
|
|
||||||
that page or not based on the permissions obtained from a Keycloak Server.
|
|
||||||
|
|
||||||
## Create the Example Realm and a Resource Server
|
|
||||||
|
|
||||||
Considering that your Keycloak Server is up and running, log in to the Keycloak Administration Console.
|
|
||||||
|
|
||||||
Now, create a new realm based on the following configuration file:
|
|
||||||
|
|
||||||
examples/authz/hello-world-authz-service/hello-world-authz-realm.json
|
|
||||||
|
|
||||||
That will import a pre-configured realm with everything you need to run this example. For more details about how to import a realm
|
|
||||||
into Keycloak, check the Keycloak's reference documentation.
|
|
||||||
|
|
||||||
After importing that file, you'll have a new realm called ``hello-world-authz``.
|
|
||||||
|
|
||||||
Now, let's import another configuration using the Administration Console in order to configure the client application ``hello-world-authz-service`` as a resource server with all resources, scopes, permissions and policies.
|
|
||||||
|
|
||||||
Click on ``Clients`` on the left side menu. Click on the ``hello-world-authz-service`` on the client listing page. This will
|
|
||||||
open the ``Client Details`` page. Once there, click on the `Authorization` tab.
|
|
||||||
|
|
||||||
Click on the ``Select file`` button, which means you want to import a resource server configuration. Now select the file that is located at:
|
|
||||||
|
|
||||||
examples/authz/hello-world-authz-service/hello-world-authz-service.json
|
|
||||||
|
|
||||||
Now click ``Upload`` and the resource server will be updated accordingly.
|
|
||||||
|
|
||||||
## Deploy and Run the Example Application
|
|
||||||
|
|
||||||
To deploy the example application, follow these steps:
|
|
||||||
|
|
||||||
cd examples/authz/hello-world-authz-service
|
|
||||||
mvn clean package wildfly:deploy
|
|
||||||
|
|
||||||
Now, try to access the client application using the following URL:
|
|
||||||
|
|
||||||
http://localhost:8080/hello-world-authz-service
|
|
||||||
|
|
||||||
If everything is correct, you will be redirect to Keycloak login page. You can login to the application with the following credentials:
|
|
||||||
|
|
||||||
* username: jdoe / password: jdoe
|
|
||||||
* username: alice / password: alice
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
{
|
|
||||||
"realm" : "hello-world-authz",
|
|
||||||
"enabled" : true,
|
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
|
||||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"requiredCredentials" : [ "password" ],
|
|
||||||
"users" :
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"username" : "alice",
|
|
||||||
"enabled" : true,
|
|
||||||
"credentials" : [ {
|
|
||||||
"type" : "password",
|
|
||||||
"value" : "alice"
|
|
||||||
} ],
|
|
||||||
"realmRoles" : ["uma_authorization"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username" : "jdoe",
|
|
||||||
"enabled" : true,
|
|
||||||
"credentials" : [ {
|
|
||||||
"type" : "password",
|
|
||||||
"value" : "jdoe"
|
|
||||||
} ],
|
|
||||||
"realmRoles" : ["uma_authorization"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username" : "service-account-hello-world-authz-service",
|
|
||||||
"enabled" : true,
|
|
||||||
"serviceAccountClientId" : "hello-world-authz-service",
|
|
||||||
"clientRoles": {
|
|
||||||
"hello-world-authz-service" : ["uma_protection"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clients" : [
|
|
||||||
{
|
|
||||||
"clientId" : "hello-world-authz-service",
|
|
||||||
"secret" : "secret",
|
|
||||||
"authorizationServicesEnabled" : true,
|
|
||||||
"enabled" : true,
|
|
||||||
"redirectUris" : [ "http://localhost:8080/hello-world-authz-service/*" ],
|
|
||||||
"baseUrl": "http://localhost:8080/hello-world-authz-service",
|
|
||||||
"adminUrl": "http://localhost:8080/hello-world-authz-service",
|
|
||||||
"directAccessGrantsEnabled" : true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
{
|
|
||||||
"allowRemoteResourceManagement": false,
|
|
||||||
"policyEnforcementMode": "ENFORCING",
|
|
||||||
"resources": [
|
|
||||||
{
|
|
||||||
"name": "Default Resource",
|
|
||||||
"uri": "/*",
|
|
||||||
"type": "urn:hello-world-authz-service:resources:default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"name": "Default Policy",
|
|
||||||
"description": "A policy that grants access only for users within this realm",
|
|
||||||
"type": "js",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "AFFIRMATIVE",
|
|
||||||
"config": {
|
|
||||||
"applyPolicies": "[]",
|
|
||||||
"code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Default Permission",
|
|
||||||
"description": "A permission that applies to the default resource type",
|
|
||||||
"type": "resource",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"defaultResourceType": "urn:hello-world-authz-service:resources:default",
|
|
||||||
"default": "true",
|
|
||||||
"applyPolicies": "[\"Default Policy\"]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-example-parent</artifactId>
|
|
||||||
<version>4.0.0.CR1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>hello-world-authz-service</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<name>Keycloak Authz: Hello World Example</name>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "hello-world-authz",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
|
||||||
"ssl-required": "external",
|
|
||||||
"resource": "hello-world-authz-service",
|
|
||||||
"credentials": {
|
|
||||||
"secret": "secret"
|
|
||||||
},
|
|
||||||
"policy-enforcer": {
|
|
||||||
"on-deny-redirect-to" : "/hello-world-authz-service/error.jsp"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
|
||||||
version="3.0">
|
|
||||||
|
|
||||||
<module-name>hello-world-authz-service</module-name>
|
|
||||||
|
|
||||||
<security-constraint>
|
|
||||||
<web-resource-collection>
|
|
||||||
<web-resource-name>All Resources</web-resource-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</web-resource-collection>
|
|
||||||
<auth-constraint>
|
|
||||||
<role-name>uma_authorization</role-name>
|
|
||||||
</auth-constraint>
|
|
||||||
</security-constraint>
|
|
||||||
|
|
||||||
<login-config>
|
|
||||||
<auth-method>KEYCLOAK</auth-method>
|
|
||||||
<realm-name>hello-world-authz</realm-name>
|
|
||||||
</login-config>
|
|
||||||
|
|
||||||
<security-role>
|
|
||||||
<role-name>uma_authorization</role-name>
|
|
||||||
</security-role>
|
|
||||||
</web-app>
|
|
|
@ -1,30 +0,0 @@
|
||||||
<%--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
~
|
|
||||||
--%>
|
|
||||||
|
|
||||||
<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
|
|
||||||
<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2><a href="<%= KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
|
||||||
.queryParam("redirect_uri", "http://localhost:8080/hello-world-authz-service").build("hello-world-authz").toString()%>">Logout</a></h2>
|
|
||||||
|
|
||||||
<h3>Access Denied !</h3>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
<%--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
~
|
|
||||||
--%>
|
|
||||||
<%@page import="org.keycloak.AuthorizationContext" %>
|
|
||||||
<%@ page import="org.keycloak.KeycloakSecurityContext" %>
|
|
||||||
<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
|
|
||||||
<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
|
|
||||||
<%@ page import="org.keycloak.representations.idm.authorization.Permission" %>
|
|
||||||
|
|
||||||
<%
|
|
||||||
KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
|
|
||||||
AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
|
|
||||||
%>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2>Welcome !</h2>
|
|
||||||
<h2><a href="<%= KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
|
||||||
.queryParam("redirect_uri", "http://localhost:8080/hello-world-authz-service").build("hello-world-authz").toString()%>">Logout</a></h2>
|
|
||||||
|
|
||||||
<h3>Your permissions are:</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<%
|
|
||||||
for (Permission permission : authzContext.getPermissions()) {
|
|
||||||
%>
|
|
||||||
<li>
|
|
||||||
<p>Resource: <%= permission.getResourceName() %></p>
|
|
||||||
<p>ID: <%= permission.getResourceId() %></p>
|
|
||||||
</li>
|
|
||||||
<%
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,49 +0,0 @@
|
||||||
{
|
|
||||||
"realm" : "hello-world-authz",
|
|
||||||
"enabled" : true,
|
|
||||||
"privateKey" : "MIIEpQIBAAKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQABAoIBAAwa4wVnKBOIS6srmYPfBTDNsTBBCEjxiYEErmn7JhoWxQ1DCPUxyxU6F177/q9Idqoj1FFOCtEO9P6/9+ym470HQmEQkR2Xxd1d3HOZy9oKuCro3ZbTDkVxY0JnlyxZz4MihGFxDH2e4MArfHy0sAgYbdIU+x2pWKGWSMzDd/TMSOExhc/sIQAg6ljbPCLLXCPQFAncoHRyGPrkRZs6UTZi5SJuCglVa2/3G+0drDdPuA83/mwsZfIBqQgbGbFgtq5T5C6CKMkPOQ42Rcclm7kEr6riTkJRo23EO1iOJVpxzI0tbxZsJAsW7zeqv0wWRyUgVfQAje6OdsNexp5aCtECgYEA6nMHCQ9xXvufCyzpIbYGxdAGqH6m1AR5gXerHqRiGNx+8UUt/E9cy/HTOhmZDK/eC4BT9tImeF01l1oSU/+wGKfux0SeAQchBhhq8GD6jmrtgczKAfZHp0Zrht7o9qu9KE7ZNWRmY1foJN9yNYmzY6qqHEy+zNo9amcqT7UZKO8CgYEA35sp9fMpMqkJE+NEJ9Ph/t2081BEkC0DYIuETZRSi+Ek5AliWTyEkg+oisTbWzi6fMQHS7W+M1SQP6djksLQNPP+353DKgup5gtKS+K/y2xNd7fSsNmkjW1bdJJpID7WzwwmwdahHxpcnFFuEXi5FkG3Vqmtd3cD0TYL33JlRy0CgYEA0+a3eybsDy9Zpp4m8IM3R98nxW8DlimdMLlafs2QpGvWiHdAgwWwF90wTxkHzgG+raKFQVbb0npcj7mnSyiUnxRZqt2H+eHZpUq4jR76F3LpzCGui2tvg+8QDMy4vwqmYyIxDCL8r9mqRnl3HpChBPoh2oY7BahTTjKEeZpzbR0CgYEAoNnVjX+mGzNNvGi4Fo5s/BIwoPcU20IGM+Uo/0W7O7Rx/Thi7x6BnzB0ZZ7GzRA51paNSQEsGXCzc5bOIjzR2cXLisDKK+zIAxwMDhrHLWZzM7OgdGeb38DTEUBhLzkE/VwYZUgoD1+/TxOkwhy9yCzt3gGhL1cF//GJCOwZvuECgYEAgsO4rdYScgCpsyePnHsFk+YtqtdORnmttF3JFcL3w2QneXuRwg2uW2Kfz8CVphrR9eOU0tiw38w6QTHIVeyRY8qqlHtiXj6dEYz7frh/k4hI29HwFx43rRpnAnN8kBEJYBYdbjaQ35Wsqkfu1tvHJ+6fxSwvQu/TVdGp0OfilAY=",
|
|
||||||
"publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQAB",
|
|
||||||
"certificate" : "MIICsTCCAZkCBgFVETX4AzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFIZWxsbyBXb3JsZCBBdXRoWjAeFw0xNjA2MDIxMzAxMzdaFw0yNjA2MDIxMzAzMTdaMBwxGjAYBgNVBAMMEUhlbGxvIFdvcmxkIEF1dGhaMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQANm5gIT/c50lwjawM686gNXpppLA928WsCOn9NIIWjSKekP8Bf9S73kf7vWcsEppm5B8rRyRxolXmzwghv74L7uVDg8Injjgj+XbPVQP+cJqWpSaMZHF7UfWe0/4M945Xcbmsl5q+m9PmrPG0AaaZhqXHcp4ehB1H+awyRqiERpJUuwZNycw2+2kjDADpsFf8hZVUd1F6ReYyOkqUyUjbL+jYTC7ZBNa7Ok+w6HCXWgkgVATAgQXJRM3w14IOc5MH/vfMCrCl/eNQLbjGl9y7u8PKwh3MXHDO2OLqtg6hOTSrOGUPJZGmGtUAl+2/R7FzoWkML/BNe2hjsL6UJwg91",
|
|
||||||
"requiredCredentials" : [ "password" ],
|
|
||||||
"users" :
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"username" : "alice",
|
|
||||||
"enabled" : true,
|
|
||||||
"credentials" : [ {
|
|
||||||
"type" : "password",
|
|
||||||
"value" : "alice"
|
|
||||||
} ],
|
|
||||||
"realmRoles" : ["uma_authorization"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username" : "jdoe",
|
|
||||||
"enabled" : true,
|
|
||||||
"credentials" : [ {
|
|
||||||
"type" : "password",
|
|
||||||
"value" : "jdoe"
|
|
||||||
} ],
|
|
||||||
"realmRoles" : ["uma_authorization"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username" : "service-account-hello-world-authz-service",
|
|
||||||
"enabled" : true,
|
|
||||||
"serviceAccountClientId" : "hello-world-authz-service",
|
|
||||||
"clientRoles": {
|
|
||||||
"hello-world-authz-service" : ["uma_protection"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clients" : [
|
|
||||||
{
|
|
||||||
"clientId" : "hello-world-authz-service",
|
|
||||||
"secret" : "secret",
|
|
||||||
"authorizationServicesEnabled" : true,
|
|
||||||
"enabled" : true,
|
|
||||||
"redirectUris" : [ "http://localhost:8080/hello-world-authz-service/*" ],
|
|
||||||
"baseUrl": "http://localhost:8080/hello-world-authz-service",
|
|
||||||
"adminUrl": "http://localhost:8080/hello-world-authz-service",
|
|
||||||
"directAccessGrantsEnabled" : true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"resources": [
|
|
||||||
{
|
|
||||||
"name": "Default Resource",
|
|
||||||
"uri": "/*",
|
|
||||||
"type": "urn:hello-world-authz-service:resources:default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"name": "Only From Realm Policy",
|
|
||||||
"description": "A policy that grants access only for users within this realm",
|
|
||||||
"type": "js",
|
|
||||||
"config": {
|
|
||||||
"applyPolicies": "[]",
|
|
||||||
"code": "var context = $evaluation.getContext();\n\n// using attributes from the evaluation context to obtain the realm\nvar contextAttributes = context.getAttributes();\nvar realmName = contextAttributes.getValue('kc.realm.name').asString(0);\n\n// using attributes from the identity to obtain the issuer\nvar identity = context.getIdentity();\nvar identityAttributes = identity.getAttributes();\nvar issuer = identityAttributes.getValue('iss').asString(0);\n\n// only users from the realm have access granted \nif (issuer.endsWith(realmName)) {\n $evaluation.grant();\n}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Default Permission",
|
|
||||||
"description": "A permission that applies to the default resource type",
|
|
||||||
"type": "resource",
|
|
||||||
"config": {
|
|
||||||
"defaultResourceType": "urn:hello-world-authz-service:resources:default",
|
|
||||||
"default": "true",
|
|
||||||
"applyPolicies": "[\"Only From Realm Policy\"]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-example-parent</artifactId>
|
|
||||||
<version>4.0.0.CR1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>keycloak-authz-hello-world</artifactId>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<name>Keycloak Authz: Hello World Example</name>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-client</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
|
@ -1,154 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* 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.keycloak.authz.helloworld;
|
|
||||||
|
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
|
||||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
|
||||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
|
||||||
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
|
||||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
|
||||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
|
||||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class AuthorizationClientExample {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
obtainEntitlementsForResource();
|
|
||||||
obtainAllEntitlements();
|
|
||||||
createResource();
|
|
||||||
updateResource();
|
|
||||||
introspectRequestingPartyToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void introspectRequestingPartyToken() {
|
|
||||||
// create a new instance based on the configuration defined in keycloak-authz.json
|
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
|
||||||
|
|
||||||
// send the authorization request to the server in order to
|
|
||||||
// obtain an RPT with all permissions granted to the user
|
|
||||||
AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize();
|
|
||||||
String rpt = response.getToken();
|
|
||||||
|
|
||||||
TokenIntrospectionResponse requestingPartyToken = authzClient.protection().introspectRequestingPartyToken(rpt);
|
|
||||||
|
|
||||||
System.out.println("Token status is: " + requestingPartyToken.getActive());
|
|
||||||
System.out.println("Permissions granted by the server: ");
|
|
||||||
|
|
||||||
for (Permission granted : requestingPartyToken.getPermissions()) {
|
|
||||||
System.out.println(granted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void createResource() {
|
|
||||||
// create a new instance based on the configuration defined in keycloak-authz.json
|
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
|
||||||
|
|
||||||
// create a new resource representation with the information we want
|
|
||||||
ResourceRepresentation newResource = new ResourceRepresentation();
|
|
||||||
|
|
||||||
newResource.setName("New Resource");
|
|
||||||
newResource.setType("urn:hello-world-authz:resources:example");
|
|
||||||
|
|
||||||
newResource.addScope(new ScopeRepresentation("urn:hello-world-authz:scopes:view"));
|
|
||||||
|
|
||||||
ProtectedResource resourceClient = authzClient.protection().resource();
|
|
||||||
ResourceRepresentation existingResource = resourceClient.findByName(newResource.getName());
|
|
||||||
|
|
||||||
if (existingResource != null) {
|
|
||||||
resourceClient.delete(existingResource.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the resource on the server
|
|
||||||
ResourceRepresentation response = resourceClient.create(newResource);
|
|
||||||
String resourceId = response.getId();
|
|
||||||
|
|
||||||
// query the resource using its newly generated id
|
|
||||||
ResourceRepresentation resource = resourceClient.findById(resourceId);
|
|
||||||
|
|
||||||
System.out.println(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void updateResource() {
|
|
||||||
// create a new instance based on the configuration defined in keycloak-authz.json
|
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
|
||||||
|
|
||||||
// create a new resource representation with the information we want
|
|
||||||
ResourceRepresentation resource = new ResourceRepresentation();
|
|
||||||
|
|
||||||
resource.setName("New Resource");
|
|
||||||
|
|
||||||
ProtectedResource resourceClient = authzClient.protection().resource();
|
|
||||||
ResourceRepresentation existingResource = resourceClient.findByName(resource.getName());
|
|
||||||
|
|
||||||
if (existingResource == null) {
|
|
||||||
createResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
resource.setId(existingResource.getId());
|
|
||||||
resource.setUri("Changed URI");
|
|
||||||
|
|
||||||
// update the resource on the server
|
|
||||||
resourceClient.update(resource);
|
|
||||||
|
|
||||||
// query the resource using its newly generated id
|
|
||||||
ResourceRepresentation existing = resourceClient.findById(resource.getId());
|
|
||||||
|
|
||||||
System.out.println(existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void obtainEntitlementsForResource() {
|
|
||||||
// create a new instance based on the configuration define at keycloak-authz.json
|
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
|
||||||
|
|
||||||
// create an authorization request
|
|
||||||
AuthorizationRequest request = new AuthorizationRequest();
|
|
||||||
|
|
||||||
// add permissions to the request based on the resources and scopes you want to check access
|
|
||||||
request.addPermission("Default Resource");
|
|
||||||
|
|
||||||
// send the entitlement request to the server in order to
|
|
||||||
// obtain an RPT with permissions for a single resource
|
|
||||||
AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize(request);
|
|
||||||
String rpt = response.getToken();
|
|
||||||
|
|
||||||
System.out.println("You got a RPT: " + rpt);
|
|
||||||
|
|
||||||
// now you can use the RPT to access protected resources on the resource server
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void obtainAllEntitlements() {
|
|
||||||
// create a new instance based on the configuration defined in keycloak-authz.json
|
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
|
||||||
|
|
||||||
// create an authorization request
|
|
||||||
AuthorizationRequest request = new AuthorizationRequest();
|
|
||||||
|
|
||||||
// send the entitlement request to the server in order to
|
|
||||||
// obtain an RPT with all permissions granted to the user
|
|
||||||
AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize(request);
|
|
||||||
String rpt = response.getToken();
|
|
||||||
|
|
||||||
System.out.println("You got a RPT: " + rpt);
|
|
||||||
|
|
||||||
// now you can use the RPT to access protected resources on the resource server
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "hello-world-authz",
|
|
||||||
"auth-server-url" : "http://localhost:8180/auth",
|
|
||||||
"resource" : "hello-world-authz-service",
|
|
||||||
"credentials": {
|
|
||||||
"secret": "secret"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
# About the Example Application
|
|
||||||
|
|
||||||
This is a simple application based on HTML5+AngularJS+JAX-RS that will introduce you to some of the main concepts around Keycloak Authorization Services.
|
|
||||||
|
|
||||||
Basically, it is a project containing three modules:
|
|
||||||
|
|
||||||
* **photoz-restful-api**, a simple RESTFul API based on JAX-RS and acting as a resource server.
|
|
||||||
* **photoz-html5-client**, a HTML5+AngularJS client that will consume the RESTful API published by a resource resourcer.
|
|
||||||
* **photoz-authz-policy**, a simple project with some rule-based policies using JBoss Drools.
|
|
||||||
|
|
||||||
For this application, users can be regular users or administrators. Regular users can create/view/delete their albums
|
|
||||||
and administrators can do anything.
|
|
||||||
|
|
||||||
In Keycloak, albums are resources that must be protected based on a set of policies that defines who and how can access them.
|
|
||||||
|
|
||||||
The resources are also associated with a set of scopes that defines a specific access context. In this case, albums have three main scopes:
|
|
||||||
|
|
||||||
* urn:photoz.com:scopes:album:create
|
|
||||||
* urn:photoz.com:scopes:album:view
|
|
||||||
* urn:photoz.com:scopes:album:delete
|
|
||||||
|
|
||||||
The authorization requirements for this example application are based on the following assumptions:
|
|
||||||
|
|
||||||
* By default, any regular user can perform any operation on his resources.
|
|
||||||
|
|
||||||
* For instance, Alice can create, view and delete her albums.
|
|
||||||
|
|
||||||
* Only the owner and administrators can delete albums. Here we are considering policies based on the *urn:photoz.com:scopes:album:delete* scope
|
|
||||||
|
|
||||||
* For instance, only Alice can delete her album.
|
|
||||||
|
|
||||||
* Only administrators can access the Administration API (which basically provides ways to query albums for all users)
|
|
||||||
|
|
||||||
* Administrators are only authorized to access resources if the client's ip address is well known
|
|
||||||
|
|
||||||
That said, this application will show you how to use the Keycloak to define policies using:
|
|
||||||
|
|
||||||
* Role-based Access Control
|
|
||||||
* Attribute-based Access Control
|
|
||||||
* Rule-based policies using JBoss Drools
|
|
||||||
* Rule-based policies using JavaScript
|
|
||||||
|
|
||||||
Beside that, this example demonstrates how to create resources dynamically and how to protected them using the *Protection API* and the *Authorization Client API*. Here you'll see
|
|
||||||
how to create a resource whose owner is the authenticated user.
|
|
||||||
|
|
||||||
It also provides some background on how you can actually protect your JAX-RS endpoints using a *policy enforcer*.
|
|
||||||
|
|
||||||
## Create the Example Realm and a Resource Server
|
|
||||||
|
|
||||||
Considering that your Keycloak Server is up and running, log in to the Keycloak Administration Console.
|
|
||||||
|
|
||||||
Now, create a new realm based on the following configuration file:
|
|
||||||
|
|
||||||
examples/authz/photoz/photoz-realm.json
|
|
||||||
|
|
||||||
That will import a pre-configured realm with everything you need to run this example. For more details about how to import a realm
|
|
||||||
into Keycloak, check the Keycloak's reference documentation.
|
|
||||||
|
|
||||||
After importing that file, you'll have a new realm called ``photoz``.
|
|
||||||
|
|
||||||
Back to the command-line, build the example application. This step is necessary given that we're using policies based on
|
|
||||||
JBoss Drools, which require ``photoz-authz-policy`` artifact installed into your local maven repository.
|
|
||||||
|
|
||||||
cd examples/authz/photoz
|
|
||||||
mvn clean install
|
|
||||||
|
|
||||||
> Please make sure you have the environment variable M2_HOME set. It should reference the path for your Maven installation. If not set, you will see some WARN messages in the logs when booting Keycloak.
|
|
||||||
|
|
||||||
Now, let's import another configuration using the Administration Console in order to configure the client application ``photoz-restful-api`` as a resource server with all resources, scopes, permissions and policies.
|
|
||||||
|
|
||||||
Click on ``Clients`` on the left side menu. Click on the ``photoz-restful-api`` on the client listing page. This will
|
|
||||||
open the ``Client Details`` page. Once there, click on the `Authorization` tab.
|
|
||||||
|
|
||||||
Click on the ``Select file`` button, which means you want to import a resource server configuration. Now select the file that is located at:
|
|
||||||
|
|
||||||
examples/authz/photoz/photoz-restful-api/src/main/resources/photoz-restful-api-authz-service.json
|
|
||||||
|
|
||||||
Now click ``Upload`` and the resource server will be updated accordingly.
|
|
||||||
|
|
||||||
## Deploy and Run the Example Applications
|
|
||||||
|
|
||||||
To deploy the example applications, follow these steps:
|
|
||||||
|
|
||||||
cd examples/authz/photoz/photoz-html5-client
|
|
||||||
mvn clean package wildfly:deploy
|
|
||||||
|
|
||||||
And then:
|
|
||||||
|
|
||||||
cd examples/authz/photoz/photoz-restful-api
|
|
||||||
mvn clean package wildfly:deploy
|
|
||||||
|
|
||||||
Now, try to access the client application using the following URL:
|
|
||||||
|
|
||||||
http://localhost:8080/photoz-html5-client
|
|
||||||
|
|
||||||
If everything is correct, you will be redirect to Keycloak login page. You can login to the application with the following credentials:
|
|
||||||
|
|
||||||
* username: jdoe / password: jdoe
|
|
||||||
* username: alice / password: alice
|
|
||||||
* username: admin / password: admin
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-photoz-parent</artifactId>
|
|
||||||
<version>4.0.0.CR1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>photoz-authz-policy</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<name>Keycloak Authz: Examples - Photoz Authz Rule-based Policy</name>
|
|
||||||
|
|
||||||
<description>
|
|
||||||
Photoz Authz Rule-based Policies using JBoss Drools
|
|
||||||
</description>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<kmodule
|
|
||||||
xmlns="http://jboss.org/kie/6.0.0/kmodule">
|
|
||||||
|
|
||||||
<kbase name="PhotozAuthzAdminPolicy" packages="com.photoz.authz.policy.admin">
|
|
||||||
<ksession name="MainAdminSession" default="true"/>
|
|
||||||
</kbase>
|
|
||||||
|
|
||||||
<kbase name="PhotozAuthzUserPolicy" packages="com.photoz.authz.policy.user">
|
|
||||||
<ksession name="MainUserSession" default="true"/>
|
|
||||||
</kbase>
|
|
||||||
|
|
||||||
<kbase name="PhotozAuthzOwnerPolicy" packages="com.photoz.authz.policy.resource.owner">
|
|
||||||
<ksession name="MainOwnerSession" default="true"/>
|
|
||||||
</kbase>
|
|
||||||
|
|
||||||
<kbase name="PhotozAuthzContextualPolicy" packages="com.photoz.authz.policy.contextual">
|
|
||||||
<ksession name="MainContextualSession" default="true"/>
|
|
||||||
</kbase>
|
|
||||||
|
|
||||||
</kmodule>
|
|
|
@ -1,14 +0,0 @@
|
||||||
package com.photoz.authz.policy.admin
|
|
||||||
|
|
||||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
|
||||||
|
|
||||||
rule "Authorize Admin Resources"
|
|
||||||
dialect "mvel"
|
|
||||||
when
|
|
||||||
$evaluation : Evaluation(
|
|
||||||
$identity : context.identity,
|
|
||||||
$identity.hasRealmRole("admin")
|
|
||||||
)
|
|
||||||
then
|
|
||||||
$evaluation.grant();
|
|
||||||
end
|
|
|
@ -1,15 +0,0 @@
|
||||||
package com.photoz.authz.policy.admin
|
|
||||||
|
|
||||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
|
||||||
|
|
||||||
rule "Authorize Resource Owner"
|
|
||||||
dialect "mvel"
|
|
||||||
when
|
|
||||||
$evaluation : Evaluation(
|
|
||||||
$identity: context.identity,
|
|
||||||
$permission: permission,
|
|
||||||
$permission.resource != null && $permission.resource.owner.equals($identity.id)
|
|
||||||
)
|
|
||||||
then
|
|
||||||
$evaluation.grant();
|
|
||||||
end
|
|
|
@ -1,14 +0,0 @@
|
||||||
package com.photoz.authz.policy.admin
|
|
||||||
|
|
||||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
|
||||||
|
|
||||||
rule "Authorize View User Album"
|
|
||||||
dialect "mvel"
|
|
||||||
when
|
|
||||||
$evaluation : Evaluation(
|
|
||||||
$identity : context.identity,
|
|
||||||
$identity.hasRealmRole("user")
|
|
||||||
)
|
|
||||||
then
|
|
||||||
$evaluation.grant();
|
|
||||||
end
|
|
|
@ -1,15 +0,0 @@
|
||||||
package com.photoz.authz.policy.admin
|
|
||||||
|
|
||||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
|
||||||
|
|
||||||
rule "Authorize Using Context Information"
|
|
||||||
dialect "mvel"
|
|
||||||
when
|
|
||||||
$evaluation : Evaluation(
|
|
||||||
$attributes: context.attributes,
|
|
||||||
$attributes.containsValue("kc.identity.authc.method", "otp"),
|
|
||||||
$attributes.containsValue("someAttribute", "you_can_access")
|
|
||||||
)
|
|
||||||
then
|
|
||||||
$evaluation.grant();
|
|
||||||
end
|
|
|
@ -1,37 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-photoz-parent</artifactId>
|
|
||||||
<version>4.0.0.CR1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>photoz-html5-client</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<name>Keycloak Authz: Photoz HTML5 Client</name>
|
|
||||||
<description>Photoz HTML5 Client</description>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>${project.artifactId}</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
|
||||||
version="3.0">
|
|
||||||
|
|
||||||
<module-name>photoz-html5-client</module-name>
|
|
||||||
|
|
||||||
</web-app>
|
|
|
@ -1,31 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Photoz HTML5 Client</title>
|
|
||||||
|
|
||||||
<!-- Load AngularJS -->
|
|
||||||
<script src="lib/angular/angular.min.js"></script>
|
|
||||||
<script src="lib/angular/angular-resource.min.js"></script>
|
|
||||||
<script src="lib/angular/angular-route.min.js"></script>
|
|
||||||
<script src="lib/jwt-decode.min.js"></script>
|
|
||||||
|
|
||||||
<script src="http://localhost:8180/auth/js/keycloak.js"></script>
|
|
||||||
<script src="http://localhost:8180/auth/js/keycloak-authz.js"></script>
|
|
||||||
<script src="js/identity.js" type="text/javascript"></script>
|
|
||||||
<script src="js/app.js" type="text/javascript"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body data-ng-controller="TokenCtrl">
|
|
||||||
|
|
||||||
<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.account()">My Account</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
|
|
||||||
|
|
||||||
<div id="content-area" class="col-md-9" role="main">
|
|
||||||
<div id="content" ng-view/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="output"></pre>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,226 +0,0 @@
|
||||||
var module = angular.module('photoz', ['ngRoute', 'ngResource']);
|
|
||||||
|
|
||||||
var resourceServerId = 'photoz-restful-api';
|
|
||||||
var apiUrl = window.location.origin + '/' + resourceServerId;
|
|
||||||
|
|
||||||
angular.element(document).ready(function ($http) {
|
|
||||||
var keycloak = new Keycloak('keycloak.json');
|
|
||||||
keycloak.init({onLoad: 'login-required'}).success(function () {
|
|
||||||
console.log('User is now authenticated.');
|
|
||||||
|
|
||||||
module.factory('Identity', function () {
|
|
||||||
return new Identity(keycloak);
|
|
||||||
});
|
|
||||||
|
|
||||||
angular.bootstrap(document, ["photoz"]);
|
|
||||||
}).error(function () {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.config(function ($httpProvider, $routeProvider) {
|
|
||||||
$httpProvider.interceptors.push('authInterceptor');
|
|
||||||
$routeProvider.when('/', {
|
|
||||||
templateUrl: 'partials/home.html',
|
|
||||||
controller: 'GlobalCtrl'
|
|
||||||
}).when('/album/create', {
|
|
||||||
templateUrl: 'partials/album/create.html',
|
|
||||||
controller: 'AlbumCtrl',
|
|
||||||
}).when('/album/:id', {
|
|
||||||
templateUrl: 'partials/album/detail.html',
|
|
||||||
controller: 'AlbumCtrl',
|
|
||||||
}).when('/admin/album', {
|
|
||||||
templateUrl: 'partials/admin/albums.html',
|
|
||||||
controller: 'AdminAlbumCtrl',
|
|
||||||
}).when('/profile', {
|
|
||||||
templateUrl: 'partials/profile.html',
|
|
||||||
controller: 'ProfileCtrl',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('GlobalCtrl', function ($scope, $http, $route, $location, Album, Identity) {
|
|
||||||
Album.query(function (albums) {
|
|
||||||
$scope.albums = albums;
|
|
||||||
});
|
|
||||||
Album.shares(function (albums) {
|
|
||||||
$scope.shares = albums;
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.Identity = Identity;
|
|
||||||
|
|
||||||
$scope.deleteAlbum = function (album) {
|
|
||||||
new Album(album).$delete({id: album.id}, function () {
|
|
||||||
$route.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.requestDeleteAccess = function (album) {
|
|
||||||
new Album(album).$delete({id: album.id}, function () {
|
|
||||||
// no-op
|
|
||||||
}, function () {
|
|
||||||
document.getElementById("output").innerHTML = 'Sent authorization request to resource owner, please, wait for approval.';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.hasAccess = function (share, scope) {
|
|
||||||
for (i = 0; i < share.scopes.length; i++) {
|
|
||||||
if (share.scopes[i] == scope) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('TokenCtrl', function ($scope, Identity) {
|
|
||||||
$scope.showRpt = function () {
|
|
||||||
document.getElementById("output").innerHTML = JSON.stringify(jwt_decode(Identity.authorization.rpt), null, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.showAccessToken = function () {
|
|
||||||
document.getElementById("output").innerHTML = JSON.stringify(jwt_decode(Identity.authc.token), null, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.requestEntitlements = function () {
|
|
||||||
Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.Identity = Identity;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
|
|
||||||
$scope.album = {};
|
|
||||||
if ($routeParams.id) {
|
|
||||||
$scope.album = Album.get({id: $routeParams.id});
|
|
||||||
}
|
|
||||||
$scope.create = function () {
|
|
||||||
var newAlbum = new Album($scope.album);
|
|
||||||
newAlbum.$save({}, function (data) {
|
|
||||||
$location.path('/');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $location, Profile) {
|
|
||||||
$scope.profile = Profile.get();
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
|
|
||||||
$scope.albums = {};
|
|
||||||
$http.get(apiUrl + '/admin/album').success(function (data) {
|
|
||||||
$scope.albums = data;
|
|
||||||
});
|
|
||||||
$scope.deleteAlbum = function (album) {
|
|
||||||
new Album(album).$delete({id: album.id}, function () {
|
|
||||||
$route.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('Album', ['$resource', function ($resource) {
|
|
||||||
return $resource(apiUrl + '/album/:id', {id: '@id'}, {
|
|
||||||
shares: {url: apiUrl + '/album/shares', method: 'GET', isArray: true}
|
|
||||||
});
|
|
||||||
}]);
|
|
||||||
|
|
||||||
module.factory('Profile', ['$resource', function ($resource) {
|
|
||||||
return $resource(apiUrl + '/profile');
|
|
||||||
}]);
|
|
||||||
|
|
||||||
module.factory('AdminAlbum', ['$resource', function ($resource) {
|
|
||||||
return $resource(apiUrl + '/admin/album/:id');
|
|
||||||
}]);
|
|
||||||
|
|
||||||
module.factory('authInterceptor', function ($q, $injector, $timeout, Identity) {
|
|
||||||
return {
|
|
||||||
request: function (request) {
|
|
||||||
document.getElementById("output").innerHTML = '';
|
|
||||||
if (Identity.authorization && Identity.authorization.rpt && request.url.indexOf('/authorize') == -1) {
|
|
||||||
retries = 0;
|
|
||||||
request.headers.Authorization = 'Bearer ' + Identity.authorization.rpt;
|
|
||||||
} else {
|
|
||||||
request.headers.Authorization = 'Bearer ' + Identity.authc.token;
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
},
|
|
||||||
responseError: function (rejection) {
|
|
||||||
var status = rejection.status;
|
|
||||||
|
|
||||||
if (status == 403 || status == 401) {
|
|
||||||
var retry = (!rejection.config.retry || rejection.config.retry < 1);
|
|
||||||
|
|
||||||
if (!retry) {
|
|
||||||
document.getElementById("output").innerHTML = 'You can not access or perform the requested operation on this resource.';
|
|
||||||
return $q.reject(rejection);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rejection.config.url.indexOf('/authorize') == -1 && retry) {
|
|
||||||
// here is the authorization logic, which tries to obtain an authorization token from the server in case the resource server
|
|
||||||
// returns a 403 or 401.
|
|
||||||
var wwwAuthenticateHeader = rejection.headers('WWW-Authenticate');
|
|
||||||
|
|
||||||
// when using UMA, a WWW-Authenticate header should be returned by the resource server
|
|
||||||
if (!wwwAuthenticateHeader) {
|
|
||||||
return $q.reject(rejection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// when using UMA, a WWW-Authenticate header should contain UMA data
|
|
||||||
if (wwwAuthenticateHeader.indexOf('UMA') == -1) {
|
|
||||||
return $q.reject(rejection);
|
|
||||||
}
|
|
||||||
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
var params = wwwAuthenticateHeader.split(',');
|
|
||||||
var ticket;
|
|
||||||
|
|
||||||
// try to extract the permission ticket from the WWW-Authenticate header
|
|
||||||
for (i = 0; i < params.length; i++) {
|
|
||||||
var param = params[i].split('=');
|
|
||||||
|
|
||||||
if (param[0] == 'ticket') {
|
|
||||||
ticket = param[1].substring(1, param[1].length - 1).trim();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a permission ticket must exist in order to send an authorization request
|
|
||||||
if (!ticket) {
|
|
||||||
return $q.reject(rejection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare a authorization request with the permission ticket
|
|
||||||
var authorizationRequest = {};
|
|
||||||
authorizationRequest.ticket = ticket;
|
|
||||||
|
|
||||||
// send the authorization request, if successful retry the request
|
|
||||||
Identity.authorization.authorize(authorizationRequest).then(function (rpt) {
|
|
||||||
deferred.resolve(rejection);
|
|
||||||
}, function () {
|
|
||||||
document.getElementById("output").innerHTML = 'You can not access or perform the requested operation on this resource.';
|
|
||||||
}, function () {
|
|
||||||
document.getElementById("output").innerHTML = 'Unexpected error from server.';
|
|
||||||
});
|
|
||||||
|
|
||||||
var promise = deferred.promise;
|
|
||||||
|
|
||||||
return promise.then(function (res) {
|
|
||||||
if (!res.config.retry) {
|
|
||||||
res.config.retry = 1;
|
|
||||||
} else {
|
|
||||||
res.config.retry++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $http = $injector.get("$http");
|
|
||||||
|
|
||||||
return $http(res.config).then(function (response) {
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $q.reject(rejection);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an Identity object holding the information obtained from the access token issued by Keycloak, after a successful authentication,
|
|
||||||
* and a few utility methods to manage it.
|
|
||||||
*/
|
|
||||||
(function (window, undefined) {
|
|
||||||
var Identity = function (keycloak) {
|
|
||||||
this.loggedIn = true;
|
|
||||||
|
|
||||||
this.claims = {};
|
|
||||||
this.claims.name = keycloak.idTokenParsed.name;
|
|
||||||
|
|
||||||
this.authc = {};
|
|
||||||
this.authc.token = keycloak.token;
|
|
||||||
|
|
||||||
this.logout = function () {
|
|
||||||
keycloak.logout();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.account = function () {
|
|
||||||
keycloak.accountManagement();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hasRole = function (name) {
|
|
||||||
if (keycloak && keycloak.hasRealmRole(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isAdmin = function () {
|
|
||||||
return this.hasRole("admin");
|
|
||||||
};
|
|
||||||
|
|
||||||
this.authorization = new KeycloakAuthorization(keycloak);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
|
|
||||||
module.exports = Identity;
|
|
||||||
} else {
|
|
||||||
window.Identity = Identity;
|
|
||||||
|
|
||||||
if ( typeof define === "function" && define.amd ) {
|
|
||||||
define( "identity", [], function () { return Identity; } );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})( window );
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "photoz",
|
|
||||||
"auth-server-url" : "http://localhost:8180/auth",
|
|
||||||
"ssl-required" : "external",
|
|
||||||
"resource" : "photoz-html5-client",
|
|
||||||
"public-client" : true
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
AngularJS v1.3.0-beta.5
|
|
||||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
License: MIT
|
|
||||||
*/
|
|
||||||
(function(H,a,A){'use strict';function D(p,g){g=g||{};a.forEach(g,function(a,c){delete g[c]});for(var c in p)!p.hasOwnProperty(c)||"$"===c.charAt(0)&&"$"===c.charAt(1)||(g[c]=p[c]);return g}var v=a.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;a.module("ngResource",["ng"]).factory("$resource",["$http","$q",function(p,g){function c(a,c){this.template=a;this.defaults=c||{};this.urlParams={}}function t(n,w,l){function r(h,d){var e={};d=x({},w,d);s(d,function(b,d){u(b)&&(b=b());var k;if(b&&
|
|
||||||
b.charAt&&"@"==b.charAt(0)){k=h;var a=b.substr(1);if(null==a||""===a||"hasOwnProperty"===a||!C.test("."+a))throw v("badmember",a);for(var a=a.split("."),f=0,c=a.length;f<c&&k!==A;f++){var g=a[f];k=null!==k?k[g]:A}}else k=b;e[d]=k});return e}function e(a){return a.resource}function f(a){D(a||{},this)}var F=new c(n);l=x({},B,l);s(l,function(h,d){var c=/^(POST|PUT|PATCH)$/i.test(h.method);f[d]=function(b,d,k,w){var q={},n,l,y;switch(arguments.length){case 4:y=w,l=k;case 3:case 2:if(u(d)){if(u(b)){l=
|
|
||||||
b;y=d;break}l=d;y=k}else{q=b;n=d;l=k;break}case 1:u(b)?l=b:c?n=b:q=b;break;case 0:break;default:throw v("badargs",arguments.length);}var t=this instanceof f,m=t?n:h.isArray?[]:new f(n),z={},B=h.interceptor&&h.interceptor.response||e,C=h.interceptor&&h.interceptor.responseError||A;s(h,function(a,b){"params"!=b&&("isArray"!=b&&"interceptor"!=b)&&(z[b]=G(a))});c&&(z.data=n);F.setUrlParams(z,x({},r(n,h.params||{}),q),h.url);q=p(z).then(function(b){var d=b.data,k=m.$promise;if(d){if(a.isArray(d)!==!!h.isArray)throw v("badcfg",
|
|
||||||
h.isArray?"array":"object",a.isArray(d)?"array":"object");h.isArray?(m.length=0,s(d,function(b){m.push(new f(b))})):(D(d,m),m.$promise=k)}m.$resolved=!0;b.resource=m;return b},function(b){m.$resolved=!0;(y||E)(b);return g.reject(b)});q=q.then(function(b){var a=B(b);(l||E)(a,b.headers);return a},C);return t?q:(m.$promise=q,m.$resolved=!1,m)};f.prototype["$"+d]=function(b,a,k){u(b)&&(k=a,a=b,b={});b=f[d].call(this,b,this,a,k);return b.$promise||b}});f.bind=function(a){return t(n,x({},w,a),l)};return f}
|
|
||||||
var B={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},E=a.noop,s=a.forEach,x=a.extend,G=a.copy,u=a.isFunction;c.prototype={setUrlParams:function(c,g,l){var r=this,e=l||r.template,f,p,h=r.urlParams={};s(e.split(/\W/),function(a){if("hasOwnProperty"===a)throw v("badname");!/^\d+$/.test(a)&&(a&&RegExp("(^|[^\\\\]):"+a+"(\\W|$)").test(e))&&(h[a]=!0)});e=e.replace(/\\:/g,":");g=g||{};s(r.urlParams,function(d,c){f=g.hasOwnProperty(c)?
|
|
||||||
g[c]:r.defaults[c];a.isDefined(f)&&null!==f?(p=encodeURIComponent(f).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),e=e.replace(RegExp(":"+c+"(\\W|$)","g"),function(a,c){return p+c})):e=e.replace(RegExp("(/?):"+c+"(\\W|$)","g"),function(a,c,d){return"/"==d.charAt(0)?d:c+d})});e=e.replace(/\/+$/,"")||"/";e=e.replace(/\/\.(?=\w+($|\?))/,".");c.url=e.replace(/\/\\\./,"/.");s(g,function(a,
|
|
||||||
e){r.urlParams[e]||(c.params=c.params||{},c.params[e]=a)})}};return t}])})(window,window.angular);
|
|
||||||
//# sourceMappingURL=angular-resource.min.js.map
|
|
|
@ -1,14 +0,0 @@
|
||||||
/*
|
|
||||||
AngularJS v1.3.0-beta.5
|
|
||||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
License: MIT
|
|
||||||
*/
|
|
||||||
(function(n,e,A){'use strict';function x(s,g,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,w){function y(){p&&(p.remove(),p=null);h&&(h.$destroy(),h=null);l&&(k.leave(l,function(){p=null}),p=l,l=null)}function v(){var b=s.current&&s.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),d=s.current;l=w(b,function(d){k.enter(d,null,l||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||g()});y()});h=d.scope=b;h.$emit("$viewContentLoaded");h.$eval(u)}else y()}
|
|
||||||
var h,l,p,t=b.autoscroll,u=b.onload||"";a.$on("$routeChangeSuccess",v);v()}}}function z(e,g,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var w=e(c.contents());b.controller&&(f.$scope=a,f=g(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));w(a)}}}n=e.module("ngRoute",["ng"]).provider("$route",function(){function s(a,c){return e.extend(new (e.extend(function(){},
|
|
||||||
{prototype:a})),c)}function g(a,e){var b=e.caseInsensitiveMatch,f={originalPath:a,regexp:a},k=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;k.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&g(a,c));if(a){var b=
|
|
||||||
"/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";k[b]=e.extend({redirectTo:a},g(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,g,n,v,h){function l(){var d=p(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!u)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)u=!1,a.$broadcast("$routeChangeStart",
|
|
||||||
d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)?c.path(t(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?g.get(d):g.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=h.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=
|
|
||||||
b,c=n.get(b,{cache:v}).then(function(a){return a.data})));e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function p(){var a,b;e.forEach(k,function(f,k){var q;if(q=!b){var g=c.path();q=f.keys;var l={};if(f.regexp)if(g=f.regexp.exec(g)){for(var h=1,p=g.length;h<p;++h){var n=q[h-1],r="string"==typeof g[h]?decodeURIComponent(g[h]):
|
|
||||||
g[h];n&&r&&(l[n.name]=r)}q=l}else q=null;else q=null;q=a=q}q&&(b=s(f,{params:e.extend({},c.search(),a),pathParams:a}),b.$$route=f)});return b||k[null]&&s(k[null],{params:{},pathParams:{}})}function t(a,c){var b=[];e.forEach((a||"").split(":"),function(a,d){if(0===d)b.push(a);else{var e=a.match(/(\w+)(.*)/),f=e[1];b.push(c[f]);b.push(e[2]||"");delete c[f]}});return b.join("")}var u=!1,r={routes:k,reload:function(){u=!0;a.$evalAsync(l)}};a.$on("$locationChangeSuccess",l);return r}]});n.provider("$routeParams",
|
|
||||||
function(){this.$get=function(){return{}}});n.directive("ngView",x);n.directive("ngView",z);x.$inject=["$route","$anchorScroll","$animate"];z.$inject=["$compile","$controller","$route"]})(window,window.angular);
|
|
||||||
//# sourceMappingURL=angular-route.min.js.map
|
|
|
@ -1,214 +0,0 @@
|
||||||
/*
|
|
||||||
AngularJS v1.3.0-beta.5
|
|
||||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
License: MIT
|
|
||||||
*/
|
|
||||||
(function(O,U,s){'use strict';function v(b){return function(){var a=arguments[0],c,a="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.3.0-beta.5/"+(b?b+"/":"")+a;for(c=1;c<arguments.length;c++)a=a+(1==c?"?":"&")+"p"+(c-1)+"="+encodeURIComponent("function"==typeof arguments[c]?arguments[c].toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof arguments[c]?"undefined":"string"!=typeof arguments[c]?JSON.stringify(arguments[c]):arguments[c]);return Error(a)}}function db(b){if(null==b||Da(b))return!1;
|
|
||||||
var a=b.length;return 1===b.nodeType&&a?!0:t(b)||M(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function q(b,a,c){var d;if(b)if(P(b))for(d in b)"prototype"==d||("length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d))||a.call(c,b[d],d);else if(b.forEach&&b.forEach!==q)b.forEach(a,c);else if(db(b))for(d=0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function Tb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function ad(b,
|
|
||||||
a,c){for(var d=Tb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function Ub(b){return function(a,c){b(c,a)}}function eb(){for(var b=ka.length,a;b;){b--;a=ka[b].charCodeAt(0);if(57==a)return ka[b]="A",ka.join("");if(90==a)ka[b]="0";else return ka[b]=String.fromCharCode(a+1),ka.join("")}ka.unshift("0");return ka.join("")}function Vb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function A(b){var a=b.$$hashKey;q(arguments,function(a){a!==b&&q(a,function(a,c){b[c]=a})});Vb(b,a);return b}function Y(b){return parseInt(b,
|
|
||||||
10)}function Wb(b,a){return A(new (A(function(){},{prototype:b})),a)}function C(){}function Ea(b){return b}function aa(b){return function(){return b}}function D(b){return"undefined"===typeof b}function B(b){return"undefined"!==typeof b}function X(b){return null!=b&&"object"===typeof b}function t(b){return"string"===typeof b}function Ab(b){return"number"===typeof b}function ra(b){return"[object Date]"===ya.call(b)}function M(b){return"[object Array]"===ya.call(b)}function P(b){return"function"===typeof b}
|
|
||||||
function fb(b){return"[object RegExp]"===ya.call(b)}function Da(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function bd(b){return!(!b||!(b.nodeName||b.prop&&b.attr&&b.find))}function cd(b,a,c){var d=[];q(b,function(b,g,f){d.push(a.call(c,b,g,f))});return d}function gb(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function Fa(b,a){var c=gb(b,a);0<=c&&b.splice(c,1);return a}function ba(b,a){if(Da(b)||b&&b.$evalAsync&&b.$watch)throw Oa("cpws");
|
|
||||||
if(a){if(b===a)throw Oa("cpi");if(M(b))for(var c=a.length=0;c<b.length;c++)a.push(ba(b[c]));else{c=a.$$hashKey;q(a,function(b,c){delete a[c]});for(var d in b)a[d]=ba(b[d]);Vb(a,c)}}else(a=b)&&(M(b)?a=ba(b,[]):ra(b)?a=new Date(b.getTime()):fb(b)?a=RegExp(b.source):X(b)&&(a=ba(b,{})));return a}function Xb(b,a){a=a||{};for(var c in b)!b.hasOwnProperty(c)||"$"===c.charAt(0)&&"$"===c.charAt(1)||(a[c]=b[c]);return a}function za(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;
|
|
||||||
var c=typeof b,d;if(c==typeof a&&"object"==c)if(M(b)){if(!M(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!za(b[d],a[d]))return!1;return!0}}else{if(ra(b))return ra(a)&&b.getTime()==a.getTime();if(fb(b)&&fb(a))return b.toString()==a.toString();if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||Da(b)||Da(a)||M(a))return!1;c={};for(d in b)if("$"!==d.charAt(0)&&!P(b[d])){if(!za(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c.hasOwnProperty(d)&&"$"!==d.charAt(0)&&a[d]!==s&&!P(a[d]))return!1;
|
|
||||||
return!0}return!1}function Yb(){return U.securityPolicy&&U.securityPolicy.isActive||U.querySelector&&!(!U.querySelector("[ng-csp]")&&!U.querySelector("[data-ng-csp]"))}function hb(b,a){var c=2<arguments.length?sa.call(arguments,2):[];return!P(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,c.concat(sa.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function dd(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)?c=
|
|
||||||
s:Da(a)?c="$WINDOW":a&&U===a?c="$DOCUMENT":a&&(a.$evalAsync&&a.$watch)&&(c="$SCOPE");return c}function ta(b,a){return"undefined"===typeof b?s:JSON.stringify(b,dd,a?" ":null)}function Zb(b){return t(b)?JSON.parse(b):b}function Pa(b){"function"===typeof b?b=!0:b&&0!==b.length?(b=I(""+b),b=!("f"==b||"0"==b||"false"==b||"no"==b||"n"==b||"[]"==b)):b=!1;return b}function ha(b){b=y(b).clone();try{b.empty()}catch(a){}var c=y("<div>").append(b).html();try{return 3===b[0].nodeType?I(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,
|
|
||||||
function(a,b){return"<"+I(b)})}catch(d){return I(c)}}function $b(b){try{return decodeURIComponent(b)}catch(a){}}function ac(b){var a={},c,d;q((b||"").split("&"),function(b){b&&(c=b.split("="),d=$b(c[0]),B(d)&&(b=B(c[1])?$b(c[1]):!0,a[d]?M(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function bc(b){var a=[];q(b,function(b,d){M(b)?q(b,function(b){a.push(Aa(d,!0)+(!0===b?"":"="+Aa(b,!0)))}):a.push(Aa(d,!0)+(!0===b?"":"="+Aa(b,!0)))});return a.length?a.join("&"):""}function Bb(b){return Aa(b,
|
|
||||||
!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Aa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function ed(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,f=["ng:app","ng-app","x-ng-app","data-ng-app"],h=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;q(f,function(a){f[a]=!0;c(U.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(q(b.querySelectorAll("."+a),c),q(b.querySelectorAll("."+
|
|
||||||
a+"\\:"),c),q(b.querySelectorAll("["+a+"]"),c))});q(d,function(a){if(!e){var b=h.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):q(a.attributes,function(b){!e&&f[b.name]&&(e=a,g=b.value)})}});e&&a(e,g?[g]:[])}function cc(b,a){var c=function(){b=y(b);if(b.injector()){var c=b[0]===U?"document":ha(b);throw Oa("btstrpd",c);}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");c=dc(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animate",
|
|
||||||
function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(O&&!d.test(O.name))return c();O.name=O.name.replace(d,"");Qa.resumeBootstrap=function(b){q(b,function(b){a.push(b)});c()}}function ib(b,a){a=a||"_";return b.replace(fd,function(b,d){return(d?a:"")+b.toLowerCase()})}function Cb(b,a,c){if(!b)throw Oa("areq",a||"?",c||"required");return b}function Ra(b,a,c){c&&M(b)&&(b=b[b.length-1]);Cb(P(b),a,"not a function, got "+(b&&"object"==typeof b?
|
|
||||||
b.constructor.name||"Object":typeof b));return b}function Ba(b,a){if("hasOwnProperty"===b)throw Oa("badname",a);}function ec(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,g=a.length,f=0;f<g;f++)d=a[f],b&&(b=(e=b)[d]);return!c&&P(b)?hb(e,b):b}function Db(b){var a=b[0];b=b[b.length-1];if(a===b)return y(a);var c=[a];do{a=a.nextSibling;if(!a)break;c.push(a)}while(a!==b);return y(c)}function gd(b){var a=v("$injector"),c=v("ng");b=b.angular||(b.angular={});b.$$minErr=b.$$minErr||v;return b.module||
|
|
||||||
(b.module=function(){var b={};return function(e,g,f){if("hasOwnProperty"===e)throw c("badname","module");g&&b.hasOwnProperty(e)&&(b[e]=null);return b[e]||(b[e]=function(){function b(a,d,e){return function(){c[e||"push"]([a,d,arguments]);return n}}if(!g)throw a("nomod",e);var c=[],d=[],l=b("$injector","invoke"),n={_invokeQueue:c,_runBlocks:d,requires:g,name:e,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:b("$provide","value"),constant:b("$provide",
|
|
||||||
"constant","unshift"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),config:l,run:function(a){d.push(a);return this}};f&&l(f);return n}())}}())}function hd(b){A(b,{bootstrap:cc,copy:ba,extend:A,equals:za,element:y,forEach:q,injector:dc,noop:C,bind:hb,toJson:ta,fromJson:Zb,identity:Ea,isUndefined:D,isDefined:B,isString:t,isFunction:P,isObject:X,isNumber:Ab,isElement:bd,isArray:M,
|
|
||||||
version:id,isDate:ra,lowercase:I,uppercase:Ga,callbacks:{counter:0},$$minErr:v,$$csp:Yb});Sa=gd(O);try{Sa("ngLocale")}catch(a){Sa("ngLocale",[]).provider("$locale",jd)}Sa("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:kd});a.provider("$compile",fc).directive({a:ld,input:gc,textarea:gc,form:md,script:nd,select:od,style:pd,option:qd,ngBind:rd,ngBindHtml:sd,ngBindTemplate:td,ngClass:ud,ngClassEven:vd,ngClassOdd:wd,ngCloak:xd,ngController:yd,ngForm:zd,ngHide:Ad,ngIf:Bd,ngInclude:Cd,
|
|
||||||
ngInit:Dd,ngNonBindable:Ed,ngPluralize:Fd,ngRepeat:Gd,ngShow:Hd,ngStyle:Id,ngSwitch:Jd,ngSwitchWhen:Kd,ngSwitchDefault:Ld,ngOptions:Md,ngTransclude:Nd,ngModel:Od,ngList:Pd,ngChange:Qd,required:hc,ngRequired:hc,ngValue:Rd}).directive({ngInclude:Sd}).directive(Eb).directive(ic);a.provider({$anchorScroll:Td,$animate:Ud,$browser:Vd,$cacheFactory:Wd,$controller:Xd,$document:Yd,$exceptionHandler:Zd,$filter:jc,$interpolate:$d,$interval:ae,$http:be,$httpBackend:ce,$location:de,$log:ee,$parse:fe,$rootScope:ge,
|
|
||||||
$q:he,$sce:ie,$sceDelegate:je,$sniffer:ke,$templateCache:le,$timeout:me,$window:ne,$$rAF:oe,$$asyncCallback:pe})}])}function Ta(b){return b.replace(qe,function(a,b,d,e){return e?d.toUpperCase():d}).replace(re,"Moz$1")}function Fb(b,a,c,d){function e(b){var e=c&&b?[this.filter(b)]:[this],m=a,k,l,n,p,r,u;if(!d||null!=b)for(;e.length;)for(k=e.shift(),l=0,n=k.length;l<n;l++)for(p=y(k[l]),m?p.triggerHandler("$destroy"):m=!m,r=0,p=(u=p.children()).length;r<p;r++)e.push(Ha(u[r]));return g.apply(this,arguments)}
|
|
||||||
var g=Ha.fn[b],g=g.$original||g;e.$original=g;Ha.fn[b]=e}function se(b,a){var c,d,e=a.createDocumentFragment(),g=[];if(Gb.test(b)){c=c||e.appendChild(a.createElement("div"));d=(te.exec(b)||["",""])[1].toLowerCase();d=ea[d]||ea._default;c.innerHTML=d[1]+b.replace(ue,"<$1></$2>")+d[2];for(d=d[0];d--;)c=c.lastChild;g=g.concat(sa.call(c.childNodes,void 0));c=e.firstChild;c.textContent=""}else g.push(a.createTextNode(b));e.textContent="";e.innerHTML="";q(g,function(a){e.appendChild(a)});return e}function N(b){if(b instanceof
|
|
||||||
N)return b;t(b)&&(b=ca(b));if(!(this instanceof N)){if(t(b)&&"<"!=b.charAt(0))throw Hb("nosel");return new N(b)}if(t(b)){var a;a=U;var c;b=(c=ve.exec(b))?[a.createElement(c[1])]:(c=se(b,a))?c.childNodes:[]}kc(this,b)}function Ib(b){return b.cloneNode(!0)}function Ia(b){lc(b);var a=0;for(b=b.childNodes||[];a<b.length;a++)Ia(b[a])}function mc(b,a,c,d){if(B(d))throw Hb("offargs");var e=la(b,"events");la(b,"handle")&&(D(a)?q(e,function(a,c){Ua(b,c,a);delete e[c]}):q(a.split(" "),function(a){D(c)?(Ua(b,
|
|
||||||
a,e[a]),delete e[a]):Fa(e[a]||[],c)}))}function lc(b,a){var c=b[jb],d=Va[c];d&&(a?delete Va[c].data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),mc(b)),delete Va[c],b[jb]=s))}function la(b,a,c){var d=b[jb],d=Va[d||-1];if(B(c))d||(b[jb]=d=++we,d=Va[d]={}),d[a]=c;else return d&&d[a]}function nc(b,a,c){var d=la(b,"data"),e=B(c),g=!e&&B(a),f=g&&!X(a);d||f||la(b,"data",d={});if(e)d[a]=c;else if(g){if(f)return d&&d[a];A(d,a)}else return d}function Jb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||
|
|
||||||
"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function kb(b,a){a&&b.setAttribute&&q(a.split(" "),function(a){b.setAttribute("class",ca((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+ca(a)+" "," ")))})}function lb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");q(a.split(" "),function(a){a=ca(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",ca(c))}}function kc(b,a){if(a){a=a.nodeName||!B(a.length)||
|
|
||||||
Da(a)?[a]:a;for(var c=0;c<a.length;c++)b.push(a[c])}}function oc(b,a){return mb(b,"$"+(a||"ngController")+"Controller")}function mb(b,a,c){b=y(b);9==b[0].nodeType&&(b=b.find("html"));for(a=M(a)?a:[a];b.length;){for(var d=b[0],e=0,g=a.length;e<g;e++)if((c=b.data(a[e]))!==s)return c;b=y(d.parentNode||11===d.nodeType&&d.host)}}function pc(b){for(var a=0,c=b.childNodes;a<c.length;a++)Ia(c[a]);for(;b.firstChild;)b.removeChild(b.firstChild)}function qc(b,a){var c=nb[a.toLowerCase()];return c&&rc[b.nodeName]&&
|
|
||||||
c}function xe(b,a){var c=function(c,e){c.preventDefault||(c.preventDefault=function(){c.returnValue=!1});c.stopPropagation||(c.stopPropagation=function(){c.cancelBubble=!0});c.target||(c.target=c.srcElement||U);if(D(c.defaultPrevented)){var g=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;g.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented||!1===c.returnValue};var f=Xb(a[e||c.type]||[]);q(f,function(a){a.call(b,c)});8>=T?(c.preventDefault=
|
|
||||||
null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function Ja(b){var a=typeof b,c;"object"==a&&null!==b?"function"==typeof(c=b.$$hashKey)?c=b.$$hashKey():c===s&&(c=b.$$hashKey=eb()):c=b;return a+":"+c}function Wa(b){q(b,this.put,this)}function sc(b){var a,c;"function"==typeof b?(a=b.$inject)||(a=[],b.length&&(c=b.toString().replace(ye,""),c=c.match(ze),q(c[1].split(Ae),function(b){b.replace(Be,function(b,
|
|
||||||
c,d){a.push(d)})})),b.$inject=a):M(b)?(c=b.length-1,Ra(b[c],"fn"),a=b.slice(0,c)):Ra(b,"fn",!0);return a}function dc(b){function a(a){return function(b,c){if(X(b))q(b,Ub(a));else return a(b,c)}}function c(a,b){Ba(a,"service");if(P(b)||M(b))b=n.instantiate(b);if(!b.$get)throw Xa("pget",a);return l[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[],c,d,g,h;q(a,function(a){if(!k.get(a)){k.put(a,!0);try{if(t(a))for(c=Sa(a),b=b.concat(e(c.requires)).concat(c._runBlocks),d=c._invokeQueue,
|
|
||||||
g=0,h=d.length;g<h;g++){var f=d[g],m=n.get(f[0]);m[f[1]].apply(m,f[2])}else P(a)?b.push(n.invoke(a)):M(a)?b.push(n.invoke(a)):Ra(a,"module")}catch(l){throw M(a)&&(a=a[a.length-1]),l.message&&(l.stack&&-1==l.stack.indexOf(l.message))&&(l=l.message+"\n"+l.stack),Xa("modulerr",a,l.stack||l.message||l);}}});return b}function g(a,b){function c(d){if(a.hasOwnProperty(d)){if(a[d]===f)throw Xa("cdep",m.join(" <- "));return a[d]}try{return m.unshift(d),a[d]=f,a[d]=b(d)}catch(e){throw a[d]===f&&delete a[d],
|
|
||||||
e;}finally{m.shift()}}function d(a,b,e){var g=[],h=sc(a),f,m,k;m=0;for(f=h.length;m<f;m++){k=h[m];if("string"!==typeof k)throw Xa("itkn",k);g.push(e&&e.hasOwnProperty(k)?e[k]:c(k))}a.$inject||(a=a[f]);return a.apply(b,g)}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=(M(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return X(e)||P(e)?e:c},get:c,annotate:sc,has:function(b){return l.hasOwnProperty(b+h)||a.hasOwnProperty(b)}}}var f={},h="Provider",m=[],k=new Wa,l={$provide:{provider:a(c),
|
|
||||||
factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,aa(b))}),constant:a(function(a,b){Ba(a,"constant");l[a]=b;p[a]=b}),decorator:function(a,b){var c=n.get(a+h),d=c.$get;c.$get=function(){var a=r.invoke(d,c);return r.invoke(b,null,{$delegate:a})}}}},n=l.$injector=g(l,function(){throw Xa("unpr",m.join(" <- "));}),p={},r=p.$injector=g(p,function(a){a=n.get(a+h);return r.invoke(a.$get,a)});q(e(b),function(a){r.invoke(a||
|
|
||||||
C)});return r}function Td(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;q(a,function(a){b||"a"!==I(a.nodeName)||(b=a)});return b}function g(){var b=c.hash(),d;b?(d=f.getElementById(b))?d.scrollIntoView():(d=e(f.getElementsByName(b)))?d.scrollIntoView():"top"===b&&a.scrollTo(0,0):a.scrollTo(0,0)}var f=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(g)});return g}]}function pe(){this.$get=
|
|
||||||
["$$rAF","$timeout",function(b,a){return b.supported?function(a){return b(a)}:function(b){return a(b,0,!1)}}]}function Ce(b,a,c,d){function e(a){try{a.apply(null,sa.call(arguments,1))}finally{if(u--,0===u)for(;z.length;)try{z.pop()()}catch(b){c.error(b)}}}function g(a,b){(function S(){q(K,function(a){a()});w=b(S,a)})()}function f(){x=null;H!=h.url()&&(H=h.url(),q(ma,function(a){a(h.url())}))}var h=this,m=a[0],k=b.location,l=b.history,n=b.setTimeout,p=b.clearTimeout,r={};h.isMock=!1;var u=0,z=[];h.$$completeOutstandingRequest=
|
|
||||||
e;h.$$incOutstandingRequestCount=function(){u++};h.notifyWhenNoOutstandingRequests=function(a){q(K,function(a){a()});0===u?a():z.push(a)};var K=[],w;h.addPollFn=function(a){D(w)&&g(100,n);K.push(a);return a};var H=k.href,G=a.find("base"),x=null;h.url=function(a,c){k!==b.location&&(k=b.location);l!==b.history&&(l=b.history);if(a){if(H!=a)return H=a,d.history?c?l.replaceState(null,"",a):(l.pushState(null,"",a),G.attr("href",G.attr("href"))):(x=a,c?k.replace(a):k.href=a),h}else return x||k.href.replace(/%27/g,
|
|
||||||
"'")};var ma=[],L=!1;h.onUrlChange=function(a){if(!L){if(d.history)y(b).on("popstate",f);if(d.hashchange)y(b).on("hashchange",f);else h.addPollFn(f);L=!0}ma.push(a);return a};h.baseHref=function(){var a=G.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};var Q={},da="",E=h.baseHref();h.cookies=function(a,b){var d,e,g,h;if(a)b===s?m.cookie=escape(a)+"=;path="+E+";expires=Thu, 01 Jan 1970 00:00:00 GMT":t(b)&&(d=(m.cookie=escape(a)+"="+escape(b)+";path="+E).length+1,4096<d&&c.warn("Cookie '"+
|
|
||||||
a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"));else{if(m.cookie!==da)for(da=m.cookie,d=da.split("; "),Q={},g=0;g<d.length;g++)e=d[g],h=e.indexOf("="),0<h&&(a=unescape(e.substring(0,h)),Q[a]===s&&(Q[a]=unescape(e.substring(h+1))));return Q}};h.defer=function(a,b){var c;u++;c=n(function(){delete r[c];e(a)},b||0);r[c]=!0;return c};h.defer.cancel=function(a){return r[a]?(delete r[a],p(a),e(C),!0):!1}}function Vd(){this.$get=["$window","$log","$sniffer","$document",
|
|
||||||
function(b,a,c,d){return new Ce(b,d,a,c)}]}function Wd(){this.$get=function(){function b(b,d){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,g(a.n,a.p),g(a,n),n=a,n.n=null)}function g(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw v("$cacheFactory")("iid",b);var f=0,h=A({},d,{id:b}),m={},k=d&&d.capacity||Number.MAX_VALUE,l={},n=null,p=null;return a[b]={put:function(a,b){if(k<Number.MAX_VALUE){var c=l[a]||(l[a]={key:a});e(c)}if(!D(b))return a in m||f++,m[a]=b,f>k&&this.remove(p.key),b},get:function(a){if(k<
|
|
||||||
Number.MAX_VALUE){var b=l[a];if(!b)return;e(b)}return m[a]},remove:function(a){if(k<Number.MAX_VALUE){var b=l[a];if(!b)return;b==n&&(n=b.p);b==p&&(p=b.n);g(b.n,b.p);delete l[a]}delete m[a];f--},removeAll:function(){m={};f=0;l={};n=p=null},destroy:function(){l=h=m=null;delete a[b]},info:function(){return A({},h,{size:f})}}}var a={};b.info=function(){var b={};q(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function le(){this.$get=["$cacheFactory",function(b){return b("templates")}]}
|
|
||||||
function fc(b,a){var c={},d="Directive",e=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,g=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,f=/^(on[a-z]+|formaction)$/;this.directive=function m(a,e){Ba(a,"directive");t(a)?(Cb(e,"directiveFactory"),c.hasOwnProperty(a)||(c[a]=[],b.factory(a+d,["$injector","$exceptionHandler",function(b,d){var e=[];q(c[a],function(c,g){try{var f=b.invoke(c);P(f)?f={compile:aa(f)}:!f.compile&&f.link&&(f.compile=aa(f.link));f.priority=f.priority||0;f.index=g;f.name=f.name||a;f.require=f.require||
|
|
||||||
f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(m){d(m)}});return e}])),c[a].push(e)):q(a,Ub(m));return this};this.aHrefSanitizationWhitelist=function(b){return B(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a.imgSrcSanitizationWhitelist(b),this):a.imgSrcSanitizationWhitelist()};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope",
|
|
||||||
"$document","$sce","$animate","$$sanitizeUri",function(a,b,l,n,p,r,u,z,K,w,H,G){function x(a,b,c,d,e){a instanceof y||(a=y(a));q(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=y(b).wrap("<span></span>").parent()[0])});var g=L(a,b,a,c,d,e);ma(a,"ng-scope");return function(b,c,d){Cb(b,"scope");var e=c?Ka.clone.call(a):a;q(d,function(a,b){e.data("$"+b+"Controller",a)});d=0;for(var f=e.length;d<f;d++){var m=e[d].nodeType;1!==m&&9!==m||e.eq(d).data("$scope",b)}c&&c(e,b);g&&g(b,e,e);return e}}
|
|
||||||
function ma(a,b){try{a.addClass(b)}catch(c){}}function L(a,b,c,d,e,g){function f(a,c,d,e){var g,k,l,n,r,p,u;g=c.length;var J=Array(g);for(r=0;r<g;r++)J[r]=c[r];u=r=0;for(p=m.length;r<p;u++)k=J[u],c=m[r++],g=m[r++],l=y(k),c?(c.scope?(n=a.$new(),l.data("$scope",n)):n=a,(l=c.transclude)||!e&&b?c(g,n,k,d,Q(a,l||b)):c(g,n,k,d,e)):g&&g(a,k.childNodes,s,e)}for(var m=[],k,l,n,r,p=0;p<a.length;p++)k=new Kb,l=da(a[p],[],k,0===p?d:s,e),(g=l.length?ia(l,a[p],k,b,c,null,[],[],g):null)&&g.scope&&ma(y(a[p]),"ng-scope"),
|
|
||||||
k=g&&g.terminal||!(n=a[p].childNodes)||!n.length?null:L(n,g?g.transclude:b),m.push(g,k),r=r||g||k,g=null;return r?f:null}function Q(a,b){return function(c,d,e){var g=!1;c||(c=a.$new(),g=c.$$transcluded=!0);d=b(c,d,e);if(g)d.on("$destroy",hb(c,c.$destroy));return d}}function da(a,b,c,d,f){var m=c.$attr,k;switch(a.nodeType){case 1:S(b,na(La(a).toLowerCase()),"E",d,f);var l,n,r;k=a.attributes;for(var p=0,u=k&&k.length;p<u;p++){var z=!1,K=!1;l=k[p];if(!T||8<=T||l.specified){n=l.name;r=na(n);W.test(r)&&
|
|
||||||
(n=ib(r.substr(6),"-"));var H=r.replace(/(Start|End)$/,"");r===H+"Start"&&(z=n,K=n.substr(0,n.length-5)+"end",n=n.substr(0,n.length-6));r=na(n.toLowerCase());m[r]=n;c[r]=l=ca(l.value);qc(a,r)&&(c[r]=!0);N(a,b,l,r);S(b,r,"A",d,f,z,K)}}a=a.className;if(t(a)&&""!==a)for(;k=g.exec(a);)r=na(k[2]),S(b,r,"C",d,f)&&(c[r]=ca(k[3])),a=a.substr(k.index+k[0].length);break;case 3:v(b,a.nodeValue);break;case 8:try{if(k=e.exec(a.nodeValue))r=na(k[1]),S(b,r,"M",d,f)&&(c[r]=ca(k[2]))}catch(x){}}b.sort(D);return b}
|
|
||||||
function E(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ja("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return y(d)}function R(a,b,c){return function(d,e,g,f,k){e=E(e[0],b,c);return a(d,e,g,f,k)}}function ia(a,c,d,e,g,f,m,n,p){function z(a,b,c,d){if(a){c&&(a=R(a,c,d));a.require=F.require;if(Q===F||F.$$isolateScope)a=uc(a,{isolateScope:!0});m.push(a)}if(b){c&&(b=R(b,c,d));b.require=F.require;
|
|
||||||
if(Q===F||F.$$isolateScope)b=uc(b,{isolateScope:!0});n.push(b)}}function K(a,b,c){var d,e="data",g=!1;if(t(a)){for(;"^"==(d=a.charAt(0))||"?"==d;)a=a.substr(1),"^"==d&&(e="inheritedData"),g=g||"?"==d;d=null;c&&"data"===e&&(d=c[a]);d=d||b[e]("$"+a+"Controller");if(!d&&!g)throw ja("ctreq",a,v);}else M(a)&&(d=[],q(a,function(a){d.push(K(a,b,c))}));return d}function H(a,e,g,f,p){function z(a,b){var c;2>arguments.length&&(b=a,a=s);A&&(c=da);return p(a,b,c)}var J,x,w,G,R,E,da={},ob;J=c===g?d:Xb(d,new Kb(y(g),
|
|
||||||
d.$attr));x=J.$$element;if(Q){var S=/^\s*([@=&])(\??)\s*(\w*)\s*$/;f=y(g);E=e.$new(!0);ia&&ia===Q.$$originalDirective?f.data("$isolateScope",E):f.data("$isolateScopeNoTemplate",E);ma(f,"ng-isolate-scope");q(Q.scope,function(a,c){var d=a.match(S)||[],g=d[3]||c,f="?"==d[2],d=d[1],m,l,n,p;E.$$isolateBindings[c]=d+g;switch(d){case "@":J.$observe(g,function(a){E[c]=a});J.$$observers[g].$$scope=e;J[g]&&(E[c]=b(J[g])(e));break;case "=":if(f&&!J[g])break;l=r(J[g]);p=l.literal?za:function(a,b){return a===
|
|
||||||
b};n=l.assign||function(){m=E[c]=l(e);throw ja("nonassign",J[g],Q.name);};m=E[c]=l(e);E.$watch(function(){var a=l(e);p(a,E[c])||(p(a,m)?n(e,a=E[c]):E[c]=a);return m=a},null,l.literal);break;case "&":l=r(J[g]);E[c]=function(a){return l(e,a)};break;default:throw ja("iscp",Q.name,c,a);}})}ob=p&&z;L&&q(L,function(a){var b={$scope:a===Q||a.$$isolateScope?E:e,$element:x,$attrs:J,$transclude:ob},c;R=a.controller;"@"==R&&(R=J[a.name]);c=u(R,b);da[a.name]=c;A||x.data("$"+a.name+"Controller",c);a.controllerAs&&
|
|
||||||
(b.$scope[a.controllerAs]=c)});f=0;for(w=m.length;f<w;f++)try{G=m[f],G(G.isolateScope?E:e,x,J,G.require&&K(G.require,x,da),ob)}catch(F){l(F,ha(x))}f=e;Q&&(Q.template||null===Q.templateUrl)&&(f=E);a&&a(f,g.childNodes,s,p);for(f=n.length-1;0<=f;f--)try{G=n[f],G(G.isolateScope?E:e,x,J,G.require&&K(G.require,x,da),ob)}catch(B){l(B,ha(x))}}p=p||{};for(var w=-Number.MAX_VALUE,G,L=p.controllerDirectives,Q=p.newIsolateScopeDirective,ia=p.templateDirective,S=p.nonTlbTranscludeDirective,D=!1,A=p.hasElementTranscludeDirective,
|
|
||||||
Z=d.$$element=y(c),F,v,V,Ya=e,O,N=0,oa=a.length;N<oa;N++){F=a[N];var T=F.$$start,W=F.$$end;T&&(Z=E(c,T,W));V=s;if(w>F.priority)break;if(V=F.scope)G=G||F,F.templateUrl||(I("new/isolated scope",Q,F,Z),X(V)&&(Q=F));v=F.name;!F.templateUrl&&F.controller&&(V=F.controller,L=L||{},I("'"+v+"' controller",L[v],F,Z),L[v]=F);if(V=F.transclude)D=!0,F.$$tlb||(I("transclusion",S,F,Z),S=F),"element"==V?(A=!0,w=F.priority,V=E(c,T,W),Z=d.$$element=y(U.createComment(" "+v+": "+d[v]+" ")),c=Z[0],pb(g,y(sa.call(V,0)),
|
|
||||||
c),Ya=x(V,e,w,f&&f.name,{nonTlbTranscludeDirective:S})):(V=y(Ib(c)).contents(),Z.empty(),Ya=x(V,e));if(F.template)if(I("template",ia,F,Z),ia=F,V=P(F.template)?F.template(Z,d):F.template,V=Y(V),F.replace){f=F;V=Gb.test(V)?y(V):[];c=V[0];if(1!=V.length||1!==c.nodeType)throw ja("tplrt",v,"");pb(g,Z,c);oa={$attr:{}};V=da(c,[],oa);var $=a.splice(N+1,a.length-(N+1));Q&&tc(V);a=a.concat(V).concat($);B(d,oa);oa=a.length}else Z.html(V);if(F.templateUrl)I("template",ia,F,Z),ia=F,F.replace&&(f=F),H=C(a.splice(N,
|
|
||||||
a.length-N),Z,d,g,Ya,m,n,{controllerDirectives:L,newIsolateScopeDirective:Q,templateDirective:ia,nonTlbTranscludeDirective:S}),oa=a.length;else if(F.compile)try{O=F.compile(Z,d,Ya),P(O)?z(null,O,T,W):O&&z(O.pre,O.post,T,W)}catch(aa){l(aa,ha(Z))}F.terminal&&(H.terminal=!0,w=Math.max(w,F.priority))}H.scope=G&&!0===G.scope;H.transclude=D&&Ya;p.hasElementTranscludeDirective=A;return H}function tc(a){for(var b=0,c=a.length;b<c;b++)a[b]=Wb(a[b],{$$isolateScope:!0})}function S(b,e,g,f,k,n,r){if(e===k)return null;
|
|
||||||
k=null;if(c.hasOwnProperty(e)){var p;e=a.get(e+d);for(var u=0,z=e.length;u<z;u++)try{p=e[u],(f===s||f>p.priority)&&-1!=p.restrict.indexOf(g)&&(n&&(p=Wb(p,{$$start:n,$$end:r})),b.push(p),k=p)}catch(K){l(K)}}return k}function B(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,g){"class"==g?(ma(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==g?(e.attr("style",e.attr("style")+";"+b),a.style=
|
|
||||||
(a.style?a.style+";":"")+b):"$"==g.charAt(0)||a.hasOwnProperty(g)||(a[g]=b,d[g]=c[g])})}function C(a,b,c,d,e,g,f,k){var m=[],l,r,u=b[0],z=a.shift(),K=A({},z,{templateUrl:null,transclude:null,replace:null,$$originalDirective:z}),x=P(z.templateUrl)?z.templateUrl(b,c):z.templateUrl;b.empty();n.get(w.getTrustedResourceUrl(x),{cache:p}).success(function(n){var p,H;n=Y(n);if(z.replace){n=Gb.test(n)?y(n):[];p=n[0];if(1!=n.length||1!==p.nodeType)throw ja("tplrt",z.name,x);n={$attr:{}};pb(d,b,p);var w=da(p,
|
|
||||||
[],n);X(z.scope)&&tc(w);a=w.concat(a);B(c,n)}else p=u,b.html(n);a.unshift(K);l=ia(a,p,c,e,b,z,g,f,k);q(d,function(a,c){a==p&&(d[c]=b[0])});for(r=L(b[0].childNodes,e);m.length;){n=m.shift();H=m.shift();var G=m.shift(),R=m.shift(),w=b[0];if(H!==u){var E=H.className;k.hasElementTranscludeDirective&&z.replace||(w=Ib(p));pb(G,y(H),w);ma(y(w),E)}H=l.transclude?Q(n,l.transclude):R;l(r,n,w,d,H)}m=null}).error(function(a,b,c,d){throw ja("tpload",d.url);});return function(a,b,c,d,e){m?(m.push(b),m.push(c),
|
|
||||||
m.push(d),m.push(e)):l(r,b,c,d,e)}}function D(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function I(a,b,c,d){if(b)throw ja("multidir",b.name,c.name,a,ha(d));}function v(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:aa(function(a,b){var c=b.parent(),e=c.data("$binding")||[];e.push(d);ma(c.data("$binding",e),"ng-binding");a.$watch(d,function(a){b[0].nodeValue=a})})})}function O(a,b){if("srcdoc"==b)return w.HTML;var c=La(a);if("xlinkHref"==b||
|
|
||||||
"FORM"==c&&"action"==b||"IMG"!=c&&("src"==b||"ngSrc"==b))return w.RESOURCE_URL}function N(a,c,d,e){var g=b(d,!0);if(g){if("multiple"===e&&"SELECT"===La(a))throw ja("selmulti",ha(a));c.push({priority:100,compile:function(){return{pre:function(c,d,m){d=m.$$observers||(m.$$observers={});if(f.test(e))throw ja("nodomevents");if(g=b(m[e],!0,O(a,e)))m[e]=g(c),(d[e]||(d[e]=[])).$$inter=!0,(m.$$observers&&m.$$observers[e].$$scope||c).$watch(g,function(a,b){"class"===e&&a!=b?m.$updateClass(a,b):m.$set(e,a)})}}}})}}
|
|
||||||
function pb(a,b,c){var d=b[0],e=b.length,g=d.parentNode,f,m;if(a)for(f=0,m=a.length;f<m;f++)if(a[f]==d){a[f++]=c;m=f+e-1;for(var k=a.length;f<k;f++,m++)m<k?a[f]=a[m]:delete a[f];a.length-=e-1;break}g&&g.replaceChild(c,d);a=U.createDocumentFragment();a.appendChild(d);c[y.expando]=d[y.expando];d=1;for(e=b.length;d<e;d++)g=b[d],y(g).remove(),a.appendChild(g),delete b[d];b[0]=c;b.length=1}function uc(a,b){return A(function(){return a.apply(null,arguments)},a,b)}var Kb=function(a,b){this.$$element=a;this.$attr=
|
|
||||||
b||{}};Kb.prototype={$normalize:na,$addClass:function(a){a&&0<a.length&&H.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&H.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=vc(a,b),d=vc(b,a);0===c.length?H.removeClass(this.$$element,d):0===d.length?H.addClass(this.$$element,c):H.setClass(this.$$element,c,d)},$set:function(a,b,c,d){var e=qc(this.$$element[0],a);e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=ib(a,
|
|
||||||
"-"));e=La(this.$$element);if("A"===e&&"href"===a||"IMG"===e&&"src"===a)this[a]=b=G(b,"src"===a);!1!==c&&(null===b||b===s?this.$$element.removeAttr(d):this.$$element.attr(d,b));(c=this.$$observers)&&q(c[a],function(a){try{a(b)}catch(c){l(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);z.$evalAsync(function(){e.$$inter||b(c[a])});return function(){Fa(e,b)}}};var Z=b.startSymbol(),oa=b.endSymbol(),Y="{{"==Z||"}}"==oa?Ea:function(a){return a.replace(/\{\{/g,
|
|
||||||
Z).replace(/}}/g,oa)},W=/^ngAttr[A-Z]/;return x}]}function na(b){return Ta(b.replace(De,""))}function vc(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),g=0;a:for(;g<d.length;g++){for(var f=d[g],h=0;h<e.length;h++)if(f==e[h])continue a;c+=(0<c.length?" ":"")+f}return c}function Xd(){var b={},a=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,d){Ba(a,"controller");X(a)?A(b,a):b[a]=d};this.$get=["$injector","$window",function(c,d){return function(e,g){var f,h,m;t(e)&&(f=e.match(a),h=f[1],m=f[3],e=
|
|
||||||
b.hasOwnProperty(h)?b[h]:ec(g.$scope,h,!0)||ec(d,h,!0),Ra(e,h,!0));f=c.instantiate(e,g);if(m){if(!g||"object"!=typeof g.$scope)throw v("$controller")("noscp",h||e.name,m);g.$scope[m]=f}return f}}]}function Yd(){this.$get=["$window",function(b){return y(b.document)}]}function Zd(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function wc(b){var a={},c,d,e;if(!b)return a;q(b.split("\n"),function(b){e=b.indexOf(":");c=I(ca(b.substr(0,e)));d=ca(b.substr(e+1));c&&(a[c]=
|
|
||||||
a[c]?a[c]+(", "+d):d)});return a}function xc(b){var a=X(b)?b:s;return function(c){a||(a=wc(b));return c?a[I(c)]||null:a}}function yc(b,a,c){if(P(c))return c(b,a);q(c,function(c){b=c(b,a)});return b}function be(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults={transformResponse:[function(d){t(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=Zb(d)));return d}],transformRequest:[function(a){return X(a)&&"[object File]"!==ya.call(a)&&
|
|
||||||
"[object Blob]"!==ya.call(a)?ta(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ba(d),put:ba(d),patch:ba(d)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},g=this.interceptors=[],f=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,n,p){function r(a){function c(a){var b=A({},a,{data:yc(a.data,a.headers,d.transformResponse)});return 200<=a.status&&300>a.status?b:n.reject(b)}var d={method:"get",
|
|
||||||
transformRequest:e.transformRequest,transformResponse:e.transformResponse},g=function(a){function b(a){var c;q(a,function(b,d){P(b)&&(c=b(),null!=c?a[d]=c:delete a[d])})}var c=e.headers,d=A({},a.headers),g,f,c=A({},c.common,c[I(a.method)]);b(c);b(d);a:for(g in c){a=I(g);for(f in d)if(I(f)===a)continue a;d[g]=c[g]}return d}(a);A(d,a);d.headers=g;d.method=Ga(d.method);(a=Lb(d.url)?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:s)&&(g[d.xsrfHeaderName||e.xsrfHeaderName]=a);var f=[function(a){g=a.headers;
|
|
||||||
var b=yc(a.data,xc(g),a.transformRequest);D(a.data)&&q(g,function(a,b){"content-type"===I(b)&&delete g[b]});D(a.withCredentials)&&!D(e.withCredentials)&&(a.withCredentials=e.withCredentials);return u(a,b,g).then(c,c)},s],h=n.when(d);for(q(w,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var k=f.shift(),h=h.then(a,k)}h.success=function(a){h.then(function(b){a(b.data,b.status,b.headers,
|
|
||||||
d)});return h};h.error=function(a){h.then(null,function(b){a(b.data,b.status,b.headers,d)});return h};return h}function u(b,c,g){function f(a,b,c,e){w&&(200<=a&&300>a?w.put(s,[a,b,wc(c),e]):w.remove(s));m(b,a,c,e);d.$$phase||d.$apply()}function m(a,c,d,e){c=Math.max(c,0);(200<=c&&300>c?p.resolve:p.reject)({data:a,status:c,headers:xc(d),config:b,statusText:e})}function k(){var a=gb(r.pendingRequests,b);-1!==a&&r.pendingRequests.splice(a,1)}var p=n.defer(),u=p.promise,w,q,s=z(b.url,b.params);r.pendingRequests.push(b);
|
|
||||||
u.then(k,k);(b.cache||e.cache)&&(!1!==b.cache&&"GET"==b.method)&&(w=X(b.cache)?b.cache:X(e.cache)?e.cache:K);if(w)if(q=w.get(s),B(q)){if(q.then)return q.then(k,k),q;M(q)?m(q[1],q[0],ba(q[2]),q[3]):m(q,200,{},"OK")}else w.put(s,u);D(q)&&a(b.method,s,c,f,g,b.timeout,b.withCredentials,b.responseType);return u}function z(a,b){if(!b)return a;var c=[];ad(b,function(a,b){null===a||D(a)||(M(a)||(a=[a]),q(a,function(a){X(a)&&(a=ta(a));c.push(Aa(b)+"="+Aa(a))}))});0<c.length&&(a+=(-1==a.indexOf("?")?"?":"&")+
|
|
||||||
c.join("&"));return a}var K=c("$http"),w=[];q(g,function(a){w.unshift(t(a)?p.get(a):p.invoke(a))});q(f,function(a,b){var c=t(a)?p.get(a):p.invoke(a);w.splice(b,0,{response:function(a){return c(n.when(a))},responseError:function(a){return c(n.reject(a))}})});r.pendingRequests=[];(function(a){q(arguments,function(a){r[a]=function(b,c){return r(A(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){r[a]=function(b,c,d){return r(A(d||{},{method:a,url:b,data:c}))}})})("post",
|
|
||||||
"put");r.defaults=e;return r}]}function Ee(b){if(8>=T&&(!b.match(/^(get|post|head|put|delete|options)$/i)||!O.XMLHttpRequest))return new O.ActiveXObject("Microsoft.XMLHTTP");if(O.XMLHttpRequest)return new O.XMLHttpRequest;throw v("$httpBackend")("noxhr");}function ce(){this.$get=["$browser","$window","$document",function(b,a,c){return Fe(b,Ee,b.defer,a.angular.callbacks,c[0])}]}function Fe(b,a,c,d,e){function g(a,b,c){var g=e.createElement("script"),f=null;g.type="text/javascript";g.src=a;g.async=
|
|
||||||
!0;f=function(a){Ua(g,"load",f);Ua(g,"error",f);e.body.removeChild(g);g=null;var h=-1,u="unknown";a&&("load"!==a.type||d[b].called||(a={type:"error"}),u=a.type,h="error"===a.type?404:200);c&&c(h,u)};qb(g,"load",f);qb(g,"error",f);e.body.appendChild(g);return f}var f=-1;return function(e,m,k,l,n,p,r,u){function z(){w=f;G&&G();x&&x.abort()}function K(a,d,e,g,f){L&&c.cancel(L);G=x=null;0===d&&(d=e?200:"file"==ua(m).protocol?404:0);a(1223===d?204:d,e,g,f||"");b.$$completeOutstandingRequest(C)}var w;b.$$incOutstandingRequestCount();
|
|
||||||
m=m||b.url();if("jsonp"==I(e)){var H="_"+(d.counter++).toString(36);d[H]=function(a){d[H].data=a;d[H].called=!0};var G=g(m.replace("JSON_CALLBACK","angular.callbacks."+H),H,function(a,b){K(l,a,d[H].data,"",b);d[H]=C})}else{var x=a(e);x.open(e,m,!0);q(n,function(a,b){B(a)&&x.setRequestHeader(b,a)});x.onreadystatechange=function(){if(x&&4==x.readyState){var a=null,b=null;w!==f&&(a=x.getAllResponseHeaders(),b="response"in x?x.response:x.responseText);K(l,w||x.status,b,a,x.statusText||"")}};r&&(x.withCredentials=
|
|
||||||
!0);if(u)try{x.responseType=u}catch(s){if("json"!==u)throw s;}x.send(k||null)}if(0<p)var L=c(z,p);else p&&p.then&&p.then(z)}}function $d(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function g(g,k,l){for(var n,p,r=0,u=[],z=g.length,K=!1,w=[];r<z;)-1!=(n=g.indexOf(b,r))&&-1!=(p=g.indexOf(a,n+f))?(r!=n&&u.push(g.substring(r,n)),u.push(r=c(K=g.substring(n+f,p))),
|
|
||||||
r.exp=K,r=p+h,K=!0):(r!=z&&u.push(g.substring(r)),r=z);(z=u.length)||(u.push(""),z=1);if(l&&1<u.length)throw zc("noconcat",g);if(!k||K)return w.length=z,r=function(a){try{for(var b=0,c=z,f;b<c;b++)"function"==typeof(f=u[b])&&(f=f(a),f=l?e.getTrusted(l,f):e.valueOf(f),null===f||D(f)?f="":"string"!=typeof f&&(f=ta(f))),w[b]=f;return w.join("")}catch(h){a=zc("interr",g,h.toString()),d(a)}},r.exp=g,r.parts=u,r}var f=b.length,h=a.length;g.startSymbol=function(){return b};g.endSymbol=function(){return a};
|
|
||||||
return g}]}function ae(){this.$get=["$rootScope","$window","$q",function(b,a,c){function d(d,f,h,m){var k=a.setInterval,l=a.clearInterval,n=c.defer(),p=n.promise,r=0,u=B(m)&&!m;h=B(h)?h:0;p.then(null,null,d);p.$$intervalId=k(function(){n.notify(r++);0<h&&r>=h&&(n.resolve(r),l(p.$$intervalId),delete e[p.$$intervalId]);u||b.$apply()},f);e[p.$$intervalId]=n;return p}var e={};d.cancel=function(a){return a&&a.$$intervalId in e?(e[a.$$intervalId].reject("canceled"),clearInterval(a.$$intervalId),delete e[a.$$intervalId],
|
|
||||||
!0):!1};return d}]}function jd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),
|
|
||||||
DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function Ac(b){b=b.split("/");for(var a=b.length;a--;)b[a]=Bb(b[a]);return b.join("/")}function Bc(b,a,c){b=ua(b,c);a.$$protocol=
|
|
||||||
b.protocol;a.$$host=b.hostname;a.$$port=Y(b.port)||Ge[b.protocol]||null}function Cc(b,a,c){var d="/"!==b.charAt(0);d&&(b="/"+b);b=ua(b,c);a.$$path=decodeURIComponent(d&&"/"===b.pathname.charAt(0)?b.pathname.substring(1):b.pathname);a.$$search=ac(b.search);a.$$hash=decodeURIComponent(b.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function pa(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Za(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Mb(b){return b.substr(0,
|
|
||||||
Za(b).lastIndexOf("/")+1)}function Dc(b,a){this.$$html5=!0;a=a||"";var c=Mb(b);Bc(b,this,b);this.$$parse=function(a){var e=pa(c,a);if(!t(e))throw Nb("ipthprfx",a,c);Cc(e,this,b);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=bc(this.$$search),b=this.$$hash?"#"+Bb(this.$$hash):"";this.$$url=Ac(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$rewrite=function(d){var e;if((e=pa(b,d))!==s)return d=e,(e=pa(a,e))!==s?c+(pa("/",e)||e):b+d;if((e=pa(c,
|
|
||||||
d))!==s)return c+e;if(c==d+"/")return c}}function Ob(b,a){var c=Mb(b);Bc(b,this,b);this.$$parse=function(d){var e=pa(b,d)||pa(c,d),e="#"==e.charAt(0)?pa(a,e):this.$$html5?e:"";if(!t(e))throw Nb("ihshprfx",d,a);Cc(e,this,b);d=this.$$path;var g=/^\/?.*?:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));g.exec(e)||(d=(e=g.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=bc(this.$$search),e=this.$$hash?"#"+Bb(this.$$hash):"";this.$$url=Ac(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=
|
|
||||||
b+(this.$$url?a+this.$$url:"")};this.$$rewrite=function(a){if(Za(b)==Za(a))return a}}function Ec(b,a){this.$$html5=!0;Ob.apply(this,arguments);var c=Mb(b);this.$$rewrite=function(d){var e;if(b==Za(d))return d;if(e=pa(c,d))return b+a+e;if(c===d+"/")return c}}function rb(b){return function(){return this[b]}}function Fc(b,a){return function(c){if(D(c))return this[b];this[b]=a(c);this.$$compose();return this}}function de(){var b="",a=!1;this.hashPrefix=function(a){return B(a)?(b=a,this):b};this.html5Mode=
|
|
||||||
function(b){return B(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,g){function f(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),a)}var h,m=d.baseHref(),k=d.url();a?(m=k.substring(0,k.indexOf("/",k.indexOf("//")+2))+(m||"/"),e=e.history?Dc:Ec):(m=Za(k),e=Ob);h=new e(m,"#"+b);h.$$parse(h.$$rewrite(k));g.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&2!=a.which){for(var b=y(a.target);"a"!==I(b[0].nodeName);)if(b[0]===g[0]||!(b=b.parent())[0])return;
|
|
||||||
var e=b.prop("href");X(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=ua(e.animVal).href);var f=h.$$rewrite(e);e&&(!b.attr("target")&&f&&!a.isDefaultPrevented())&&(a.preventDefault(),f!=d.url()&&(h.$$parse(f),c.$apply(),O.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=k&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);c.$broadcast("$locationChangeStart",a,b).defaultPrevented?(h.$$parse(b),d.url(b)):f(b)}),c.$$phase||
|
|
||||||
c.$digest())});var l=0;c.$watch(function(){var a=d.url(),b=h.$$replace;l&&a==h.absUrl()||(l++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),f(a))}));h.$$replace=!1;return l});return h}]}function ee(){var b=!0,a=this;this.debugEnabled=function(a){return B(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:
|
|
||||||
a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||C;a=!1;try{a=!!e.apply}catch(m){}return a?function(){var a=[];q(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function fa(b,a){if("constructor"===b)throw Ca("isecfld",a);return b}function $a(b,
|
|
||||||
a){if(b){if(b.constructor===b)throw Ca("isecfn",a);if(b.document&&b.location&&b.alert&&b.setInterval)throw Ca("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw Ca("isecdom",a);}return b}function sb(b,a,c,d,e){e=e||{};a=a.split(".");for(var g,f=0;1<a.length;f++){g=fa(a.shift(),d);var h=b[g];h||(h={},b[g]=h);b=h;b.then&&e.unwrapPromises&&(va(d),"$$v"in b||function(a){a.then(function(b){a.$$v=b})}(b),b.$$v===s&&(b.$$v={}),b=b.$$v)}g=fa(a.shift(),d);return b[g]=c}function Gc(b,
|
|
||||||
a,c,d,e,g,f){fa(b,g);fa(a,g);fa(c,g);fa(d,g);fa(e,g);return f.unwrapPromises?function(f,m){var k=m&&m.hasOwnProperty(b)?m:f,l;if(null==k)return k;(k=k[b])&&k.then&&(va(g),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);if(!a)return k;if(null==k)return s;(k=k[a])&&k.then&&(va(g),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);if(!c)return k;if(null==k)return s;(k=k[c])&&k.then&&(va(g),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);if(!d)return k;if(null==
|
|
||||||
k)return s;(k=k[d])&&k.then&&(va(g),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);if(!e)return k;if(null==k)return s;(k=k[e])&&k.then&&(va(g),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);return k}:function(g,f){var k=f&&f.hasOwnProperty(b)?f:g;if(null==k)return k;k=k[b];if(!a)return k;if(null==k)return s;k=k[a];if(!c)return k;if(null==k)return s;k=k[c];if(!d)return k;if(null==k)return s;k=k[d];return e?null==k?s:k=k[e]:k}}function He(b,a){fa(b,a);return function(a,
|
|
||||||
d){return null==a?s:(d&&d.hasOwnProperty(b)?d:a)[b]}}function Ie(b,a,c){fa(b,c);fa(a,c);return function(c,e){if(null==c)return s;c=(e&&e.hasOwnProperty(b)?e:c)[b];return null==c?s:c[a]}}function Hc(b,a,c){if(Pb.hasOwnProperty(b))return Pb[b];var d=b.split("."),e=d.length,g;if(a.unwrapPromises||1!==e)if(a.unwrapPromises||2!==e)if(a.csp)g=6>e?Gc(d[0],d[1],d[2],d[3],d[4],c,a):function(b,g){var f=0,h;do h=Gc(d[f++],d[f++],d[f++],d[f++],d[f++],c,a)(b,g),g=s,b=h;while(f<e);return h};else{var f="var p;\n";
|
|
||||||
q(d,function(b,d){fa(b,c);f+="if(s == null) return undefined;\ns="+(d?"s":'((k&&k.hasOwnProperty("'+b+'"))?k:s)')+'["'+b+'"];\n'+(a.unwrapPromises?'if (s && s.then) {\n pw("'+c.replace(/(["\r\n])/g,"\\$1")+'");\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n':"")});var f=f+"return s;",h=new Function("s","k","pw",f);h.toString=aa(f);g=a.unwrapPromises?function(a,b){return h(a,b,va)}:h}else g=Ie(d[0],d[1],c);else g=He(d[0],c);"hasOwnProperty"!==
|
|
||||||
b&&(Pb[b]=g);return g}function fe(){var b={},a={csp:!1,unwrapPromises:!1,logPromiseWarnings:!0};this.unwrapPromises=function(b){return B(b)?(a.unwrapPromises=!!b,this):a.unwrapPromises};this.logPromiseWarnings=function(b){return B(b)?(a.logPromiseWarnings=b,this):a.logPromiseWarnings};this.$get=["$filter","$sniffer","$log",function(c,d,e){a.csp=d.csp;va=function(b){a.logPromiseWarnings&&!Ic.hasOwnProperty(b)&&(Ic[b]=!0,e.warn("[$parse] Promise found in the expression `"+b+"`. Automatic unwrapping of promises in Angular expressions is deprecated."))};
|
|
||||||
return function(d){var e;switch(typeof d){case "string":if(b.hasOwnProperty(d))return b[d];e=new Qb(a);e=(new ab(e,c,a)).parse(d,!1);"hasOwnProperty"!==d&&(b[d]=e);return e;case "function":return d;default:return C}}}]}function he(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return Je(function(a){b.$evalAsync(a)},a)}]}function Je(b,a){function c(a){return a}function d(a){return f(a)}var e=function(){var f=[],k,l;return l={resolve:function(a){if(f){var c=f;f=s;k=g(a);c.length&&b(function(){for(var a,
|
|
||||||
b=0,d=c.length;b<d;b++)a=c[b],k.then(a[0],a[1],a[2])})}},reject:function(a){l.resolve(h(a))},notify:function(a){if(f){var c=f;f.length&&b(function(){for(var b,d=0,e=c.length;d<e;d++)b=c[d],b[2](a)})}},promise:{then:function(b,g,h){var l=e(),z=function(d){try{l.resolve((P(b)?b:c)(d))}catch(e){l.reject(e),a(e)}},K=function(b){try{l.resolve((P(g)?g:d)(b))}catch(c){l.reject(c),a(c)}},w=function(b){try{l.notify((P(h)?h:c)(b))}catch(d){a(d)}};f?f.push([z,K,w]):k.then(z,K,w);return l.promise},"catch":function(a){return this.then(null,
|
|
||||||
a)},"finally":function(a){function b(a,c){var d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,g){var f=null;try{f=(a||c)()}catch(h){return b(h,!1)}return f&&P(f.then)?f.then(function(){return b(e,g)},function(a){return b(a,!1)}):b(e,g)}return this.then(function(a){return d(a,!0)},function(a){return d(a,!1)})}}}},g=function(a){return a&&P(a.then)?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},f=function(a){var b=e();b.reject(a);return b.promise},h=function(c){return{then:function(g,
|
|
||||||
f){var h=e();b(function(){try{h.resolve((P(f)?f:d)(c))}catch(b){h.reject(b),a(b)}});return h.promise}}};return{defer:e,reject:f,when:function(h,k,l,n){var p=e(),r,u=function(b){try{return(P(k)?k:c)(b)}catch(d){return a(d),f(d)}},z=function(b){try{return(P(l)?l:d)(b)}catch(c){return a(c),f(c)}},K=function(b){try{return(P(n)?n:c)(b)}catch(d){a(d)}};b(function(){g(h).then(function(a){r||(r=!0,p.resolve(g(a).then(u,z,K)))},function(a){r||(r=!0,p.resolve(z(a)))},function(a){r||p.notify(K(a))})});return p.promise},
|
|
||||||
all:function(a){var b=e(),c=0,d=M(a)?[]:{};q(a,function(a,e){c++;g(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise}}}function oe(){this.$get=["$window","$timeout",function(b,a){var c=b.requestAnimationFrame||b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame,d=b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.mozCancelAnimationFrame||b.webkitCancelRequestAnimationFrame,e=!!c,g=e?
|
|
||||||
function(a){var b=c(a);return function(){d(b)}}:function(b){var c=a(b,16.66,!1);return function(){a.cancel(c)}};g.supported=e;return g}]}function ge(){var b=10,a=v("$rootScope"),c=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse","$browser",function(d,e,g,f){function h(){this.$id=eb();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;
|
|
||||||
this.$$destroyed=!1;this.$$asyncQueue=[];this.$$postDigestQueue=[];this.$$listeners={};this.$$listenerCount={};this.$$isolateBindings={}}function m(b){if(p.$$phase)throw a("inprog",p.$$phase);p.$$phase=b}function k(a,b){var c=g(a);Ra(c,b);return c}function l(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function n(){}h.prototype={constructor:h,$new:function(a){a?(a=new h,a.$root=this.$root,a.$$asyncQueue=this.$$asyncQueue,a.$$postDigestQueue=
|
|
||||||
this.$$postDigestQueue):(a=function(){},a.prototype=this,a=new a,a.$id=eb());a["this"]=a;a.$$listeners={};a.$$listenerCount={};a.$parent=this;a.$$watchers=a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,d){var e=k(a,"watch"),g=this.$$watchers,f={fn:b,last:n,get:e,exp:a,eq:!!d};c=null;if(!P(b)){var h=k(b||C,"listener");f.fn=function(a,
|
|
||||||
b,c){h(c)}}if("string"==typeof a&&e.constant){var m=f.fn;f.fn=function(a,b,c){m.call(this,a,b,c);Fa(g,f)}}g||(g=this.$$watchers=[]);g.unshift(f);return function(){Fa(g,f);c=null}},$watchCollection:function(a,b){var c=this,d,e,f,h=1<b.length,k=0,m=g(a),l=[],n={},p=!0,q=0;return this.$watch(function(){d=m(c);var a,b;if(X(d))if(db(d))for(e!==l&&(e=l,q=e.length=0,k++),a=d.length,q!==a&&(k++,e.length=q=a),b=0;b<a;b++)e[b]!==e[b]&&d[b]!==d[b]||e[b]===d[b]||(k++,e[b]=d[b]);else{e!==n&&(e=n={},q=0,k++);a=
|
|
||||||
0;for(b in d)d.hasOwnProperty(b)&&(a++,e.hasOwnProperty(b)?e[b]!==d[b]&&(k++,e[b]=d[b]):(q++,e[b]=d[b],k++));if(q>a)for(b in k++,e)e.hasOwnProperty(b)&&!d.hasOwnProperty(b)&&(q--,delete e[b])}else e!==d&&(e=d,k++);return k},function(){p?(p=!1,b(d,d,c)):b(d,f,c);if(h)if(X(d))if(db(d)){f=Array(d.length);for(var a=0;a<d.length;a++)f[a]=d[a]}else for(a in f={},d)Jc.call(d,a)&&(f[a]=d[a]);else f=d})},$digest:function(){var d,g,f,h,k=this.$$asyncQueue,l=this.$$postDigestQueue,q,x,s=b,L,Q=[],y,E,R;m("$digest");
|
|
||||||
c=null;do{x=!1;for(L=this;k.length;){try{R=k.shift(),R.scope.$eval(R.expression)}catch(B){p.$$phase=null,e(B)}c=null}a:do{if(h=L.$$watchers)for(q=h.length;q--;)try{if(d=h[q])if((g=d.get(L))!==(f=d.last)&&!(d.eq?za(g,f):"number"==typeof g&&"number"==typeof f&&isNaN(g)&&isNaN(f)))x=!0,c=d,d.last=d.eq?ba(g):g,d.fn(g,f===n?g:f,L),5>s&&(y=4-s,Q[y]||(Q[y]=[]),E=P(d.exp)?"fn: "+(d.exp.name||d.exp.toString()):d.exp,E+="; newVal: "+ta(g)+"; oldVal: "+ta(f),Q[y].push(E));else if(d===c){x=!1;break a}}catch(t){p.$$phase=
|
|
||||||
null,e(t)}if(!(h=L.$$childHead||L!==this&&L.$$nextSibling))for(;L!==this&&!(h=L.$$nextSibling);)L=L.$parent}while(L=h);if((x||k.length)&&!s--)throw p.$$phase=null,a("infdig",b,ta(Q));}while(x||k.length);for(p.$$phase=null;l.length;)try{l.shift()()}catch(S){e(S)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this!==p&&(q(this.$$listenerCount,hb(null,l,this)),a.$$childHead==this&&(a.$$childHead=this.$$nextSibling),a.$$childTail==this&&
|
|
||||||
(a.$$childTail=this.$$prevSibling),this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling),this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling),this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=null,this.$$listeners={},this.$$watchers=this.$$asyncQueue=this.$$postDigestQueue=[],this.$destroy=this.$digest=this.$apply=C,this.$on=this.$watch=function(){return C})}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a){p.$$phase||
|
|
||||||
p.$$asyncQueue.length||f.defer(function(){p.$$asyncQueue.length&&p.$digest()});this.$$asyncQueue.push({scope:this,expression:a})},$$postDigest:function(a){this.$$postDigestQueue.push(a)},$apply:function(a){try{return m("$apply"),this.$eval(a)}catch(b){e(b)}finally{p.$$phase=null;try{p.$digest()}catch(c){throw e(c),c;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);
|
|
||||||
var e=this;return function(){c[gb(c,b)]=null;l(e,1,a)}},$emit:function(a,b){var c=[],d,g=this,f=!1,h={name:a,targetScope:g,stopPropagation:function(){f=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=[h].concat(sa.call(arguments,1)),m,l;do{d=g.$$listeners[a]||c;h.currentScope=g;m=0;for(l=d.length;m<l;m++)if(d[m])try{d[m].apply(null,k)}catch(n){e(n)}else d.splice(m,1),m--,l--;if(f)break;g=g.$parent}while(g);return h},$broadcast:function(a,b){for(var c=this,d=this,g={name:a,
|
|
||||||
targetScope:this,preventDefault:function(){g.defaultPrevented=!0},defaultPrevented:!1},f=[g].concat(sa.call(arguments,1)),h,k;c=d;){g.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,f)}catch(m){e(m)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}return g}};var p=new h;return p}]}function kd(){var b=/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*(https?|ftp|file|blob):|data:image\//;
|
|
||||||
this.aHrefSanitizationWhitelist=function(a){return B(a)?(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,g;if(!T||8<=T)if(g=ua(c).href,""!==g&&!g.match(e))return"unsafe:"+g;return c}}}function Ke(b){if("self"===b)return b;if(t(b)){if(-1<b.indexOf("***"))throw wa("iwcard",b);b=b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08").replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return RegExp("^"+
|
|
||||||
b+"$")}if(fb(b))return RegExp("^"+b.source+"$");throw wa("imatcher");}function Kc(b){var a=[];B(b)&&q(b,function(b){a.push(Ke(b))});return a}function je(){this.SCE_CONTEXTS=ga;var b=["self"],a=[];this.resourceUrlWhitelist=function(a){arguments.length&&(b=Kc(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=Kc(b));return a};this.$get=["$injector",function(c){function d(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=
|
|
||||||
function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var e=function(a){throw wa("unsafe");};c.has("$sanitize")&&(e=c.get("$sanitize"));var g=d(),f={};f[ga.HTML]=d(g);f[ga.CSS]=d(g);f[ga.URL]=d(g);f[ga.JS]=d(g);f[ga.RESOURCE_URL]=d(f[ga.URL]);return{trustAs:function(a,b){var c=f.hasOwnProperty(a)?f[a]:null;if(!c)throw wa("icontext",a,b);if(null===b||b===s||""===b)return b;if("string"!==typeof b)throw wa("itype",a);return new c(b)},
|
|
||||||
getTrusted:function(c,d){if(null===d||d===s||""===d)return d;var g=f.hasOwnProperty(c)?f[c]:null;if(g&&d instanceof g)return d.$$unwrapTrustedValue();if(c===ga.RESOURCE_URL){var g=ua(d.toString()),l,n,p=!1;l=0;for(n=b.length;l<n;l++)if("self"===b[l]?Lb(g):b[l].exec(g.href)){p=!0;break}if(p)for(l=0,n=a.length;l<n;l++)if("self"===a[l]?Lb(g):a[l].exec(g.href)){p=!1;break}if(p)return d;throw wa("insecurl",d.toString());}if(c===ga.HTML)return e(d);throw wa("unsafe");},valueOf:function(a){return a instanceof
|
|
||||||
g?a.$$unwrapTrustedValue():a}}}]}function ie(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sniffer","$sceDelegate",function(a,c,d){if(b&&c.msie&&8>c.msieDocumentMode)throw wa("iequirks");var e=ba(ga);e.isEnabled=function(){return b};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;b||(e.trustAs=e.getTrusted=function(a,b){return b},e.valueOf=Ea);e.parseAs=function(b,c){var d=a(c);return d.literal&&d.constant?d:function(a,c){return e.getTrusted(b,
|
|
||||||
d(a,c))}};var g=e.parseAs,f=e.getTrusted,h=e.trustAs;q(ga,function(a,b){var c=I(b);e[Ta("parse_as_"+c)]=function(b){return g(a,b)};e[Ta("get_trusted_"+c)]=function(b){return f(a,b)};e[Ta("trust_as_"+c)]=function(b){return h(a,b)}});return e}]}function ke(){this.$get=["$window","$document",function(b,a){var c={},d=Y((/android (\d+)/.exec(I((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),g=a[0]||{},f=g.documentMode,h,m=/^(Moz|webkit|O|ms)(?=[A-Z])/,k=g.body&&g.body.style,
|
|
||||||
l=!1,n=!1;if(k){for(var p in k)if(l=m.exec(p)){h=l[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in k&&"webkit");l=!!("transition"in k||h+"Transition"in k);n=!!("animation"in k||h+"Animation"in k);!d||l&&n||(l=t(g.body.style.webkitTransition),n=t(g.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hashchange:"onhashchange"in b&&(!f||7<f),hasEvent:function(a){if("input"==a&&9==T)return!1;if(D(c[a])){var b=g.createElement("div");c[a]="on"+
|
|
||||||
a in b}return c[a]},csp:Yb(),vendorPrefix:h,transitions:l,animations:n,android:d,msie:T,msieDocumentMode:f}}]}function me(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,h,m){var k=c.defer(),l=k.promise,n=B(m)&&!m;h=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a),d(a)}finally{delete g[l.$$timeoutId]}n||b.$apply()},h);l.$$timeoutId=h;g[h]=k;return l}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),
|
|
||||||
delete g[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return e}]}function ua(b,a){var c=b;T&&(W.setAttribute("href",c),c=W.href);W.setAttribute("href",c);return{href:W.href,protocol:W.protocol?W.protocol.replace(/:$/,""):"",host:W.host,search:W.search?W.search.replace(/^\?/,""):"",hash:W.hash?W.hash.replace(/^#/,""):"",hostname:W.hostname,port:W.port,pathname:"/"===W.pathname.charAt(0)?W.pathname:"/"+W.pathname}}function Lb(b){b=t(b)?ua(b):b;return b.protocol===Lc.protocol&&b.host===Lc.host}
|
|
||||||
function ne(){this.$get=aa(O)}function jc(b){function a(d,e){if(X(d)){var g={};q(d,function(b,c){g[c]=a(c,b)});return g}return b.factory(d+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Mc);a("date",Nc);a("filter",Le);a("json",Me);a("limitTo",Ne);a("lowercase",Oe);a("number",Oc);a("orderBy",Pc);a("uppercase",Pe)}function Le(){return function(b,a,c){if(!M(b))return b;var d=typeof c,e=[];e.check=function(a){for(var b=0;b<e.length;b++)if(!e[b](a))return!1;
|
|
||||||
return!0};"function"!==d&&(c="boolean"===d&&c?function(a,b){return Qa.equals(a,b)}:function(a,b){if(a&&b&&"object"===typeof a&&"object"===typeof b){for(var d in a)if("$"!==d.charAt(0)&&Jc.call(a,d)&&c(a[d],b[d]))return!0;return!1}b=(""+b).toLowerCase();return-1<(""+a).toLowerCase().indexOf(b)});var g=function(a,b){if("string"==typeof b&&"!"===b.charAt(0))return!g(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,
|
|
||||||
b);default:for(var d in a)if("$"!==d.charAt(0)&&g(a[d],b))return!0}return!1;case "array":for(d=0;d<a.length;d++)if(g(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a={$:a};case "object":for(var f in a)(function(b){"undefined"!=typeof a[b]&&e.push(function(c){return g("$"==b?c:c&&c[b],a[b])})})(f);break;case "function":e.push(a);break;default:return b}d=[];for(f=0;f<b.length;f++){var h=b[f];e.check(h)&&d.push(h)}return d}}function Mc(b){var a=
|
|
||||||
b.NUMBER_FORMATS;return function(b,d){D(d)&&(d=a.CURRENCY_SYM);return Qc(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function Oc(b){var a=b.NUMBER_FORMATS;return function(b,d){return Qc(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Qc(b,a,c,d,e){if(null==b||!isFinite(b)||X(b))return"";var g=0>b;b=Math.abs(b);var f=b+"",h="",m=[],k=!1;if(-1!==f.indexOf("e")){var l=f.match(/([\d\.]+)e(-?)(\d+)/);l&&"-"==l[2]&&l[3]>e+1?f="0":(h=f,k=!0)}if(k)0<e&&(-1<b&&1>b)&&(h=b.toFixed(e));
|
|
||||||
else{f=(f.split(Rc)[1]||"").length;D(e)&&(e=Math.min(Math.max(a.minFrac,f),a.maxFrac));f=Math.pow(10,e);b=Math.round(b*f)/f;b=(""+b).split(Rc);f=b[0];b=b[1]||"";var l=0,n=a.lgSize,p=a.gSize;if(f.length>=n+p)for(l=f.length-n,k=0;k<l;k++)0===(l-k)%p&&0!==k&&(h+=c),h+=f.charAt(k);for(k=l;k<f.length;k++)0===(f.length-k)%n&&0!==k&&(h+=c),h+=f.charAt(k);for(;b.length<e;)b+="0";e&&"0"!==e&&(h+=d+b.substr(0,e))}m.push(g?a.negPre:a.posPre);m.push(h);m.push(g?a.negSuf:a.posSuf);return m.join("")}function tb(b,
|
|
||||||
a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function $(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(0<c||e>-c)e+=c;0===e&&-12==c&&(e=12);return tb(e,a,d)}}function ub(b,a){return function(c,d){var e=c["get"+b](),g=Ga(a?"SHORT"+b:b);return d[g][e]}}function Sc(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function Tc(b){return function(a){var c=Sc(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+
|
|
||||||
(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return tb(a,b)}}function Nc(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var g=0,f=0,h=b[8]?a.setUTCFullYear:a.setFullYear,m=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=Y(b[9]+b[10]),f=Y(b[9]+b[11]));h.call(a,Y(b[1]),Y(b[2])-1,Y(b[3]));g=Y(b[4]||0)-g;f=Y(b[5]||0)-f;h=Y(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));m.call(a,g,f,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
|
|
||||||
return function(c,e){var g="",f=[],h,m;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;t(c)&&(c=Qe.test(c)?Y(c):a(c));Ab(c)&&(c=new Date(c));if(!ra(c))return c;for(;e;)(m=Re.exec(e))?(f=f.concat(sa.call(m,1)),e=f.pop()):(f.push(e),e=null);q(f,function(a){h=Se[a];g+=h?h(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Me(){return function(b){return ta(b,!0)}}function Ne(){return function(b,a){if(!M(b)&&!t(b))return b;a=Y(a);if(t(b))return a?0<=a?b.slice(0,a):b.slice(a,
|
|
||||||
b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);0<a?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Pc(b){return function(a,c,d){function e(a,b){return Pa(b)?function(b,c){return a(c,b)}:a}function g(a,b){var c=typeof a,d=typeof b;return c==d?("string"==c&&(a=a.toLowerCase(),b=b.toLowerCase()),a===b?0:a<b?-1:1):c<d?-1:1}if(!M(a)||!c)return a;c=M(c)?c:[c];c=cd(c,function(a){var c=!1,d=a||Ea;if(t(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))c=
|
|
||||||
"-"==a.charAt(0),a=a.substring(1);d=b(a);if(d.constant){var f=d();return e(function(a,b){return g(a[f],b[f])},c)}}return e(function(a,b){return g(d(a),d(b))},c)});for(var f=[],h=0;h<a.length;h++)f.push(a[h]);return f.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(0!==e)return e}return 0},d))}}function xa(b){P(b)&&(b={link:b});b.restrict=b.restrict||"AC";return aa(b)}function Uc(b,a,c,d){function e(a,c){c=c?"-"+ib(c,"-"):"";d.removeClass(b,(a?vb:wb)+c);d.addClass(b,(a?wb:vb)+c)}
|
|
||||||
var g=this,f=b.parent().controller("form")||xb,h=0,m=g.$error={},k=[];g.$name=a.name||a.ngForm;g.$dirty=!1;g.$pristine=!0;g.$valid=!0;g.$invalid=!1;f.$addControl(g);b.addClass(Ma);e(!0);g.$addControl=function(a){Ba(a.$name,"input");k.push(a);a.$name&&(g[a.$name]=a)};g.$removeControl=function(a){a.$name&&g[a.$name]===a&&delete g[a.$name];q(m,function(b,c){g.$setValidity(c,!0,a)});Fa(k,a)};g.$setValidity=function(a,b,c){var d=m[a];if(b)d&&(Fa(d,c),d.length||(h--,h||(e(b),g.$valid=!0,g.$invalid=!1),
|
|
||||||
m[a]=!1,e(!0,a),f.$setValidity(a,!0,g)));else{h||e(b);if(d){if(-1!=gb(d,c))return}else m[a]=d=[],h++,e(!1,a),f.$setValidity(a,!1,g);d.push(c);g.$valid=!1;g.$invalid=!0}};g.$setDirty=function(){d.removeClass(b,Ma);d.addClass(b,yb);g.$dirty=!0;g.$pristine=!1;f.$setDirty()};g.$setPristine=function(){d.removeClass(b,yb);d.addClass(b,Ma);g.$dirty=!1;g.$pristine=!0;q(k,function(a){a.$setPristine()})}}function qa(b,a,c,d){b.$setValidity(a,c);return c?d:s}function Te(b,a,c){var d=c.prop("validity");X(d)&&
|
|
||||||
b.$parsers.push(function(c){if(b.$error[a]||!(d.badInput||d.customError||d.typeMismatch)||d.valueMissing)return c;b.$setValidity(a,!1)})}function bb(b,a,c,d,e,g){var f=a.prop("validity");if(!e.android){var h=!1;a.on("compositionstart",function(a){h=!0});a.on("compositionend",function(){h=!1;m()})}var m=function(){if(!h){var e=a.val();Pa(c.ngTrim||"T")&&(e=ca(e));if(d.$viewValue!==e||f&&""===e&&!f.valueMissing)b.$$phase?d.$setViewValue(e):b.$apply(function(){d.$setViewValue(e)})}};if(e.hasEvent("input"))a.on("input",
|
|
||||||
m);else{var k,l=function(){k||(k=g.defer(function(){m();k=null}))};a.on("keydown",function(a){a=a.keyCode;91===a||(15<a&&19>a||37<=a&&40>=a)||l()});if(e.hasEvent("paste"))a.on("paste cut",l)}a.on("change",m);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)};var n=c.ngPattern;n&&((e=n.match(/^\/(.*)\/([gim]*)$/))?(n=RegExp(e[1],e[2]),e=function(a){return qa(d,"pattern",d.$isEmpty(a)||n.test(a),a)}):e=function(c){var e=b.$eval(n);if(!e||!e.test)throw v("ngPattern")("noregexp",n,
|
|
||||||
e,ha(a));return qa(d,"pattern",d.$isEmpty(c)||e.test(c),c)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var p=Y(c.ngMinlength);e=function(a){return qa(d,"minlength",d.$isEmpty(a)||a.length>=p,a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var r=Y(c.ngMaxlength);e=function(a){return qa(d,"maxlength",d.$isEmpty(a)||a.length<=r,a)};d.$parsers.push(e);d.$formatters.push(e)}}function zb(b,a){return function(c){var d;return ra(c)?c:t(c)&&(b.lastIndex=0,c=b.exec(c))?(c.shift(),
|
|
||||||
d={yyyy:0,MM:1,dd:1,HH:0,mm:0},q(c,function(b,c){c<a.length&&(d[a[c]]=+b)}),new Date(d.yyyy,d.MM-1,d.dd,d.HH,d.mm)):NaN}}function cb(b,a,c,d){return function(e,g,f,h,m,k,l){bb(e,g,f,h,m,k);h.$parsers.push(function(d){if(h.$isEmpty(d))return h.$setValidity(b,!0),null;if(a.test(d))return h.$setValidity(b,!0),c(d);h.$setValidity(b,!1);return s});h.$formatters.push(function(a){return ra(a)?l("date")(a,d):""});f.min&&(e=function(a){var b=h.$isEmpty(a)||c(a)>=c(f.min);h.$setValidity("min",b);return b?a:
|
|
||||||
s},h.$parsers.push(e),h.$formatters.push(e));f.max&&(e=function(a){var b=h.$isEmpty(a)||c(a)<=c(f.max);h.$setValidity("max",b);return b?a:s},h.$parsers.push(e),h.$formatters.push(e))}}function Rb(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],l=0;l<b.length;l++)if(e==b[l])continue a;c.push(e)}return c}function e(a){if(!M(a)){if(t(a))return a.split(" ");if(X(a)){var b=[];q(a,function(a,c){a&&b.push(c)});return b}}return a}return{restrict:"AC",
|
|
||||||
link:function(g,f,h){function m(a,b){var c=f.data("$classCounts")||{},d=[];q(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});f.data("$classCounts",c);return d.join(" ")}function k(b){if(!0===a||g.$index%2===a){var k=e(b||[]);if(!l){var r=m(k,1);h.$addClass(r)}else if(!za(b,l)){var q=e(l),r=d(k,q),k=d(q,k),k=m(k,-1),r=m(r,1);0===r.length?c.removeClass(f,k):0===k.length?c.addClass(f,r):c.setClass(f,r,k)}}l=ba(b)}var l;g.$watch(h[b],k,!0);h.$observe("class",function(a){k(g.$eval(h[b]))});
|
|
||||||
"ngClass"!==b&&g.$watch("$index",function(c,d){var f=c&1;if(f!==d&1){var k=e(g.$eval(h[b]));f===a?(f=m(k,1),h.$addClass(f)):(f=m(k,-1),h.$removeClass(f))}})}}}]}var I=function(b){return t(b)?b.toLowerCase():b},Jc=Object.prototype.hasOwnProperty,Ga=function(b){return t(b)?b.toUpperCase():b},T,y,Ha,sa=[].slice,Ue=[].push,ya=Object.prototype.toString,Oa=v("ng"),Qa=O.angular||(O.angular={}),Sa,La,ka=["0","0","0"];T=Y((/msie (\d+)/.exec(I(navigator.userAgent))||[])[1]);isNaN(T)&&(T=Y((/trident\/.*; rv:(\d+)/.exec(I(navigator.userAgent))||
|
|
||||||
[])[1]));C.$inject=[];Ea.$inject=[];var ca=function(){return String.prototype.trim?function(b){return t(b)?b.trim():b}:function(b){return t(b)?b.replace(/^\s\s*/,"").replace(/\s\s*$/,""):b}}();La=9>T?function(b){b=b.nodeName?b:b[0];return b.scopeName&&"HTML"!=b.scopeName?Ga(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var fd=/[A-Z]/g,id={full:"1.3.0-beta.5",major:1,minor:3,dot:0,codeName:"chimeric-glitterfication"},Va=N.cache={},jb=N.expando="ng-"+
|
|
||||||
(new Date).getTime(),we=1,qb=O.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},Ua=O.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)};N._data=function(b){return this.cache[b[this.expando]]||{}};var qe=/([\:\-\_]+(.))/g,re=/^moz([A-Z])/,Hb=v("jqLite"),ve=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,Gb=/<|&#?\w+;/,te=/<([\w:]+)/,ue=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
|
|
||||||
ea={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ea.optgroup=ea.option;ea.tbody=ea.tfoot=ea.colgroup=ea.caption=ea.thead;ea.th=ea.td;var Ka=N.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===U.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),N(O).on("load",a))},
|
|
||||||
toString:function(){var b=[];q(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?y(this[b]):y(this[this.length+b])},length:0,push:Ue,sort:[].sort,splice:[].splice},nb={};q("multiple selected checked disabled readOnly required open".split(" "),function(b){nb[I(b)]=b});var rc={};q("input select option textarea button form details".split(" "),function(b){rc[Ga(b)]=!0});q({data:nc,inheritedData:mb,scope:function(b){return y(b).data("$scope")||mb(b.parentNode||b,["$isolateScope",
|
|
||||||
"$scope"])},isolateScope:function(b){return y(b).data("$isolateScope")||y(b).data("$isolateScopeNoTemplate")},controller:oc,injector:function(b){return mb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Jb,css:function(b,a,c){a=Ta(a);if(B(c))b.style[a]=c;else{var d;8>=T&&(d=b.currentStyle&&b.currentStyle[a],""===d&&(d="auto"));d=d||b.style[a];8>=T&&(d=""===d?s:d);return d}},attr:function(b,a,c){var d=I(a);if(nb[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));
|
|
||||||
else return b[a]||(b.attributes.getNamedItem(a)||C).specified?d:s;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?s:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:function(){function b(b,d){var e=a[b.nodeType];if(D(d))return e?b[e]:"";b[e]=d}var a=[];9>T?(a[1]="innerText",a[3]="nodeValue"):a[1]=a[3]="textContent";b.$dv="";return b}(),val:function(b,a){if(D(a)){if("SELECT"===La(b)&&b.multiple){var c=[];q(b.options,function(a){a.selected&&
|
|
||||||
c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(D(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)Ia(d[c]);b.innerHTML=a},empty:pc},function(b,a){N.prototype[a]=function(a,d){var e,g;if(b!==pc&&(2==b.length&&b!==Jb&&b!==oc?a:d)===s){if(X(a)){for(e=0;e<this.length;e++)if(b===nc)b(this[e],a);else for(g in a)b(this[e],g,a[g]);return this}e=b.$dv;g=e===s?Math.min(this.length,1):this.length;for(var f=0;f<g;f++){var h=b(this[f],a,d);e=
|
|
||||||
e?e+h:h}return e}for(e=0;e<this.length;e++)b(this[e],a,d);return this}});q({removeData:lc,dealoc:Ia,on:function a(c,d,e,g){if(B(g))throw Hb("onargs");var f=la(c,"events"),h=la(c,"handle");f||la(c,"events",f={});h||la(c,"handle",h=xe(c,f));q(d.split(" "),function(d){var g=f[d];if(!g){if("mouseenter"==d||"mouseleave"==d){var l=U.body.contains||U.body.compareDocumentPosition?function(a,c){var d=9===a.nodeType?a.documentElement:a,e=c&&c.parentNode;return a===e||!!(e&&1===e.nodeType&&(d.contains?d.contains(e):
|
|
||||||
a.compareDocumentPosition&&a.compareDocumentPosition(e)&16))}:function(a,c){if(c)for(;c=c.parentNode;)if(c===a)return!0;return!1};f[d]=[];a(c,{mouseleave:"mouseout",mouseenter:"mouseover"}[d],function(a){var c=a.relatedTarget;c&&(c===this||l(this,c))||h(a,d)})}else qb(c,d,h),f[d]=[];g=f[d]}g.push(e)})},off:mc,one:function(a,c,d){a=y(a);a.on(c,function g(){a.off(c,d);a.off(c,g)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;Ia(a);q(new N(c),function(c){d?e.insertBefore(c,d.nextSibling):
|
|
||||||
e.replaceChild(c,a);d=c})},children:function(a){var c=[];q(a.childNodes,function(a){1===a.nodeType&&c.push(a)});return c},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,c){q(new N(c),function(c){1!==a.nodeType&&11!==a.nodeType||a.appendChild(c)})},prepend:function(a,c){if(1===a.nodeType){var d=a.firstChild;q(new N(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=y(c)[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){Ia(a);
|
|
||||||
var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;q(new N(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:lb,removeClass:kb,toggleClass:function(a,c,d){c&&q(c.split(" "),function(c){var g=d;D(g)&&(g=!Jb(a,c));(g?lb:kb)(a,c)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;null!=a&&1!==a.nodeType;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName?
|
|
||||||
a.getElementsByTagName(c):[]},clone:Ib,triggerHandler:function(a,c,d){c=(la(a,"events")||{})[c];d=d||[];var e=[{preventDefault:C,stopPropagation:C}];q(c,function(c){c.apply(a,e.concat(d))})}},function(a,c){N.prototype[c]=function(c,e,g){for(var f,h=0;h<this.length;h++)D(f)?(f=a(this[h],c,e,g),B(f)&&(f=y(f))):kc(f,a(this[h],c,e,g));return B(f)?f:this};N.prototype.bind=N.prototype.on;N.prototype.unbind=N.prototype.off});Wa.prototype={put:function(a,c){this[Ja(a)]=c},get:function(a){return this[Ja(a)]},
|
|
||||||
remove:function(a){var c=this[a=Ja(a)];delete this[a];return c}};var ze=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,Ae=/,/,Be=/^\s*(_?)(\S+?)\1\s*$/,ye=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Xa=v("$injector"),Ve=v("$animate"),Ud=["$provide",function(a){this.$$selectors={};this.register=function(c,d){var e=c+"-animation";if(c&&"."!=c.charAt(0))throw Ve("notcsel",c);this.$$selectors[c.substr(1)]=e;a.factory(e,d)};this.classNameFilter=function(a){1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?
|
|
||||||
a:null);return this.$$classNameFilter};this.$get=["$timeout","$$asyncCallback",function(a,d){return{enter:function(a,c,f,h){f?f.after(a):c.prepend(a);h&&d(h)},leave:function(a,c){a.remove();c&&d(c)},move:function(a,c,d,h){this.enter(a,c,d,h)},addClass:function(a,c,f){c=t(c)?c:M(c)?c.join(" "):"";q(a,function(a){lb(a,c)});f&&d(f)},removeClass:function(a,c,f){c=t(c)?c:M(c)?c.join(" "):"";q(a,function(a){kb(a,c)});f&&d(f)},setClass:function(a,c,f,h){q(a,function(a){lb(a,c);kb(a,f)});h&&d(h)},enabled:C}}]}],
|
|
||||||
ja=v("$compile");fc.$inject=["$provide","$$sanitizeUriProvider"];var De=/^(x[\:\-_]|data[\:\-_])/i,zc=v("$interpolate"),We=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,Ge={http:80,https:443,ftp:21},Nb=v("$location");Ec.prototype=Ob.prototype=Dc.prototype={$$html5:!1,$$replace:!1,absUrl:rb("$$absUrl"),url:function(a,c){if(D(a))return this.$$url;var d=We.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));(d[2]||d[1])&&this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:rb("$$protocol"),host:rb("$$host"),
|
|
||||||
port:rb("$$port"),path:Fc("$$path",function(a){return"/"==a.charAt(0)?a:"/"+a}),search:function(a,c){switch(arguments.length){case 0:return this.$$search;case 1:if(t(a))this.$$search=ac(a);else if(X(a))this.$$search=a;else throw Nb("isrcharg");break;default:D(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:Fc("$$hash",Ea),replace:function(){this.$$replace=!0;return this}};var Ca=v("$parse"),Ic={},va,Na={"null":function(){return null},"true":function(){return!0},
|
|
||||||
"false":function(){return!1},undefined:C,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:s},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":C,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,c,d,e){return d(a,c)==e(a,
|
|
||||||
c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Xe={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},
|
|
||||||
Qb=function(a){this.options=a};Qb.prototype={constructor:Qb,lex:function(a){this.text=a;this.index=0;this.ch=s;this.lastCh=":";this.tokens=[];var c;for(a=[];this.index<this.text.length;){this.ch=this.text.charAt(this.index);if(this.is("\"'"))this.readString(this.ch);else if(this.isNumber(this.ch)||this.is(".")&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(this.ch))this.readIdent(),this.was("{,")&&("{"===a[0]&&(c=this.tokens[this.tokens.length-1]))&&(c.json=-1===c.text.indexOf("."));
|
|
||||||
else if(this.is("(){}[].,;:?"))this.tokens.push({index:this.index,text:this.ch,json:this.was(":[,")&&this.is("{[")||this.is("}]:,")}),this.is("{[")&&a.unshift(this.ch),this.is("}]")&&a.shift(),this.index++;else if(this.isWhitespace(this.ch)){this.index++;continue}else{var d=this.ch+this.peek(),e=d+this.peek(2),g=Na[this.ch],f=Na[d],h=Na[e];h?(this.tokens.push({index:this.index,text:e,fn:h}),this.index+=3):f?(this.tokens.push({index:this.index,text:d,fn:f}),this.index+=2):g?(this.tokens.push({index:this.index,
|
|
||||||
text:this.ch,fn:g,json:this.was("[,:")&&this.is("+-")}),this.index+=1):this.throwError("Unexpected next character ",this.index,this.index+1)}this.lastCh=this.ch}return this.tokens},is:function(a){return-1!==a.indexOf(this.ch)},was:function(a){return-1!==a.indexOf(this.lastCh)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===
|
|
||||||
a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=B(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw Ca("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=I(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=
|
|
||||||
d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}a*=1;this.tokens.push({index:c,text:a,json:!0,fn:function(){return a}})},readIdent:function(){for(var a=this,c="",d=this.index,e,g,f,h;this.index<this.text.length;){h=this.text.charAt(this.index);if("."===h||this.isIdent(h)||this.isNumber(h))"."===h&&(e=this.index),c+=h;else break;
|
|
||||||
this.index++}if(e)for(g=this.index;g<this.text.length;){h=this.text.charAt(g);if("("===h){f=c.substr(e-d+1);c=c.substr(0,e-d);this.index=g;break}if(this.isWhitespace(h))g++;else break}d={index:d,text:c};if(Na.hasOwnProperty(c))d.fn=Na[c],d.json=Na[c];else{var m=Hc(c,this.options,this.text);d.fn=A(function(a,c){return m(a,c)},{assign:function(d,e){return sb(d,c,e,a.text,a.options)}})}this.tokens.push(d);f&&(this.tokens.push({index:e,text:".",json:!1}),this.tokens.push({index:e+1,text:f,json:!1}))},
|
|
||||||
readString:function(a){var c=this.index;this.index++;for(var d="",e=a,g=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),e=e+f;if(g)"u"===f?(f=this.text.substring(this.index+1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d=(g=Xe[f])?d+g:d+f,g=!1;else if("\\"===f)g=!0;else{if(f===a){this.index++;this.tokens.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});return}d+=
|
|
||||||
f}this.index++}this.throwError("Unterminated quote",c)}};var ab=function(a,c,d){this.lexer=a;this.$filter=c;this.options=d};ab.ZERO=A(function(){return 0},{constant:!0});ab.prototype={constructor:ab,parse:function(a,c){this.text=a;this.json=c;this.tokens=this.lexer.lex(a);c&&(this.assignment=this.logicalOR,this.functionCall=this.fieldAccess=this.objectIndex=this.filterChain=function(){this.throwError("is not valid json",{text:a,index:0})});var d=c?this.primary():this.statements();0!==this.tokens.length&&
|
|
||||||
this.throwError("is an unexpected token",this.tokens[0]);d.literal=!!d.literal;d.constant=!!d.constant;return d},primary:function(){var a;if(this.expect("("))a=this.filterChain(),this.consume(")");else if(this.expect("["))a=this.arrayDeclaration();else if(this.expect("{"))a=this.object();else{var c=this.expect();(a=c.fn)||this.throwError("not a primary expression",c);c.json&&(a.constant=!0,a.literal=!0)}for(var d;c=this.expect("(","[",".");)"("===c.text?(a=this.functionCall(a,d),d=null):"["===c.text?
|
|
||||||
(d=a,a=this.objectIndex(a)):"."===c.text?(d=a,a=this.fieldAccess(a)):this.throwError("IMPOSSIBLE");return a},throwError:function(a,c){throw Ca("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},peekToken:function(){if(0===this.tokens.length)throw Ca("ueoe",this.text);return this.tokens[0]},peek:function(a,c,d,e){if(0<this.tokens.length){var g=this.tokens[0],f=g.text;if(f===a||f===c||f===d||f===e||!(a||c||d||e))return g}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,
|
|
||||||
e))?(this.json&&!a.json&&this.throwError("is not valid json",a),this.tokens.shift(),a):!1},consume:function(a){this.expect(a)||this.throwError("is unexpected, expecting ["+a+"]",this.peek())},unaryFn:function(a,c){return A(function(d,e){return a(d,e,c)},{constant:c.constant})},ternaryFn:function(a,c,d){return A(function(e,g){return a(e,g)?c(e,g):d(e,g)},{constant:a.constant&&c.constant&&d.constant})},binaryFn:function(a,c,d){return A(function(e,g){return c(e,g,a,d)},{constant:a.constant&&d.constant})},
|
|
||||||
statements:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.filterChain()),!this.expect(";"))return 1===a.length?a[0]:function(c,d){for(var e,g=0;g<a.length;g++){var f=a[g];f&&(e=f(c,d))}return e}},filterChain:function(){for(var a=this.expression(),c;;)if(c=this.expect("|"))a=this.binaryFn(a,c.fn,this.filter());else return a},filter:function(){for(var a=this.expect(),c=this.$filter(a.text),d=[];;)if(a=this.expect(":"))d.push(this.expression());else{var e=
|
|
||||||
function(a,e,h){h=[h];for(var m=0;m<d.length;m++)h.push(d[m](a,e));return c.apply(a,h)};return function(){return e}}},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary(),c,d;return(d=this.expect("="))?(a.assign||this.throwError("implies assignment but ["+this.text.substring(0,d.index)+"] can not be assigned to",d),c=this.ternary(),function(d,g){return a.assign(d,c(d,g),g)}):a},ternary:function(){var a=this.logicalOR(),c,d;if(this.expect("?")){c=this.ternary();
|
|
||||||
if(d=this.expect(":"))return this.ternaryFn(a,c,this.ternary());this.throwError("expected :",d)}else return a},logicalOR:function(){for(var a=this.logicalAND(),c;;)if(c=this.expect("||"))a=this.binaryFn(a,c.fn,this.logicalAND());else return a},logicalAND:function(){var a=this.equality(),c;if(c=this.expect("&&"))a=this.binaryFn(a,c.fn,this.logicalAND());return a},equality:function(){var a=this.relational(),c;if(c=this.expect("==","!=","===","!=="))a=this.binaryFn(a,c.fn,this.equality());return a},
|
|
||||||
relational:function(){var a=this.additive(),c;if(c=this.expect("<",">","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(ab.ZERO,a.fn,
|
|
||||||
this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this,d=this.expect().text,e=Hc(d,this.options,this.text);return A(function(c,d,h){return e(h||a(c,d))},{assign:function(e,f,h){return sb(a(e,h),d,f,c.text,c.options)}})},objectIndex:function(a){var c=this,d=this.expression();this.consume("]");return A(function(e,g){var f=a(e,g),h=d(e,g),m;if(!f)return s;(f=$a(f[h],c.text))&&(f.then&&c.options.unwrapPromises)&&(m=f,"$$v"in f||(m.$$v=s,m.then(function(a){m.$$v=
|
|
||||||
a})),f=f.$$v);return f},{assign:function(e,g,f){var h=d(e,f);return $a(a(e,f),c.text)[h]=g}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this;return function(g,f){for(var h=[],m=c?c(g,f):g,k=0;k<d.length;k++)h.push(d[k](g,f));k=a(g,f,m)||C;$a(m,e.text);$a(k,e.text);h=k.apply?k.apply(m,h):k(h[0],h[1],h[2],h[3],h[4]);return $a(h,e.text)}},arrayDeclaration:function(){var a=[],c=!0;if("]"!==this.peekToken().text){do{if(this.peek("]"))break;
|
|
||||||
var d=this.expression();a.push(d);d.constant||(c=!1)}while(this.expect(","))}this.consume("]");return A(function(c,d){for(var f=[],h=0;h<a.length;h++)f.push(a[h](c,d));return f},{literal:!0,constant:c})},object:function(){var a=[],c=!0;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;var d=this.expect(),d=d.string||d.text;this.consume(":");var e=this.expression();a.push({key:d,value:e});e.constant||(c=!1)}while(this.expect(","))}this.consume("}");return A(function(c,d){for(var e={},m=0;m<
|
|
||||||
a.length;m++){var k=a[m];e[k.key]=k.value(c,d)}return e},{literal:!0,constant:c})}};var Pb={},wa=v("$sce"),ga={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},W=U.createElement("a"),Lc=ua(O.location.href,!0);jc.$inject=["$provide"];Mc.$inject=["$locale"];Oc.$inject=["$locale"];var Rc=".",Se={yyyy:$("FullYear",4),yy:$("FullYear",2,0,!0),y:$("FullYear",1),MMMM:ub("Month"),MMM:ub("Month",!0),MM:$("Month",2,1),M:$("Month",1,1),dd:$("Date",2),d:$("Date",1),HH:$("Hours",2),H:$("Hours",
|
|
||||||
1),hh:$("Hours",2,-12),h:$("Hours",1,-12),mm:$("Minutes",2),m:$("Minutes",1),ss:$("Seconds",2),s:$("Seconds",1),sss:$("Milliseconds",3),EEEE:ub("Day"),EEE:ub("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(tb(Math[0<a?"floor":"ceil"](a/60),2)+tb(Math.abs(a%60),2))},ww:Tc(2),w:Tc(1)},Re=/((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,Qe=/^\-?\d+$/;Nc.$inject=["$locale"];var Oe=
|
|
||||||
aa(I),Pe=aa(Ga);Pc.$inject=["$parse"];var ld=aa({restrict:"E",compile:function(a,c){8>=T&&(c.href||c.name||c.$set("href",""),a.append(U.createComment("IE fix")));if(!c.href&&!c.xlinkHref&&!c.name)return function(a,c){var g="[object SVGAnimatedString]"===ya.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(g)||a.preventDefault()})}}}),Eb={};q(nb,function(a,c){if("multiple"!=a){var d=na("ng-"+c);Eb[d]=function(){return{priority:100,link:function(a,g,f){a.$watch(f[d],function(a){f.$set(c,
|
|
||||||
!!a)})}}}}});q(["src","srcset","href"],function(a){var c=na("ng-"+a);Eb[c]=function(){return{priority:99,link:function(d,e,g){var f=a,h=a;"href"===a&&"[object SVGAnimatedString]"===ya.call(e.prop("href"))&&(h="xlinkHref",g.$attr[h]="xlink:href",f=null);g.$observe(c,function(a){a&&(g.$set(h,a),T&&f&&e.prop(f,g[h]))})}}}});var xb={$addControl:C,$removeControl:C,$setValidity:C,$setDirty:C,$setPristine:C};Uc.$inject=["$element","$attrs","$scope","$animate"];var Vc=function(a){return["$timeout",function(c){return{name:"form",
|
|
||||||
restrict:a?"EAC":"E",controller:Uc,compile:function(){return{pre:function(a,e,g,f){if(!g.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};qb(e[0],"submit",h);e.on("$destroy",function(){c(function(){Ua(e[0],"submit",h)},0,!1)})}var m=e.parent().controller("form"),k=g.name||g.ngForm;k&&sb(a,k,f,k);if(m)e.on("$destroy",function(){m.$removeControl(f);k&&sb(a,k,s,k);A(f,xb)})}}}}}]},md=Vc(),zd=Vc(!0),Ye=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,
|
|
||||||
Ze=/^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i,$e=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,Wc=/^(\d{4})-(\d{2})-(\d{2})$/,Xc=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)$/,Sb=/^(\d{4})-W(\d\d)$/,Yc=/^(\d{4})-(\d\d)$/,Zc=/^(\d\d):(\d\d)$/,$c={text:bb,date:cb("date",Wc,zb(Wc,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":cb("datetimelocal",Xc,zb(Xc,["yyyy","MM","dd","HH","mm"]),"yyyy-MM-ddTHH:mm"),time:cb("time",Zc,zb(Zc,["HH","mm"]),"HH:mm"),week:cb("week",Sb,function(a){if(ra(a))return a;
|
|
||||||
if(t(a)){Sb.lastIndex=0;var c=Sb.exec(a);if(c){a=+c[1];var d=+c[2],c=Sc(a),d=7*(d-1);return new Date(a,0,c.getDate()+d)}}return NaN},"yyyy-Www"),month:cb("month",Yc,zb(Yc,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,g,f){bb(a,c,d,e,g,f);e.$parsers.push(function(a){var c=e.$isEmpty(a);if(c||$e.test(a))return e.$setValidity("number",!0),""===a?null:c?a:parseFloat(a);e.$setValidity("number",!1);return s});Te(e,"number",c);e.$formatters.push(function(a){return e.$isEmpty(a)?"":""+a});d.min&&(a=function(a){var c=
|
|
||||||
parseFloat(d.min);return qa(e,"min",e.$isEmpty(a)||a>=c,a)},e.$parsers.push(a),e.$formatters.push(a));d.max&&(a=function(a){var c=parseFloat(d.max);return qa(e,"max",e.$isEmpty(a)||a<=c,a)},e.$parsers.push(a),e.$formatters.push(a));e.$formatters.push(function(a){return qa(e,"number",e.$isEmpty(a)||Ab(a),a)})},url:function(a,c,d,e,g,f){bb(a,c,d,e,g,f);a=function(a){return qa(e,"url",e.$isEmpty(a)||Ye.test(a),a)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,f){bb(a,c,d,e,g,f);
|
|
||||||
a=function(a){return qa(e,"email",e.$isEmpty(a)||Ze.test(a),a)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){D(d.name)&&c.attr("name",eb());c.on("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,f=d.ngFalseValue;t(g)||(g=!0);t(f)||(f=!1);c.on("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});
|
|
||||||
e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==g};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:f})},hidden:C,button:C,submit:C,reset:C,file:C},gc=["$browser","$sniffer","$filter",function(a,c,d){return{restrict:"E",require:"?ngModel",link:function(e,g,f,h){h&&($c[I(f.type)]||$c.text)(e,g,f,h,c,a,d)}}}],wb="ng-valid",vb="ng-invalid",Ma="ng-pristine",yb="ng-dirty",af=["$scope","$exceptionHandler","$attrs","$element","$parse",
|
|
||||||
"$animate",function(a,c,d,e,g,f){function h(a,c){c=c?"-"+ib(c,"-"):"";f.removeClass(e,(a?vb:wb)+c);f.addClass(e,(a?wb:vb)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var m=g(d.ngModel),k=m.assign;if(!k)throw v("ngModel")("nonassign",d.ngModel,ha(e));this.$render=C;this.$isEmpty=function(a){return D(a)||""===a||null===a||a!==a};var l=e.inheritedData("$formController")||
|
|
||||||
xb,n=0,p=this.$error={};e.addClass(Ma);h(!0);this.$setValidity=function(a,c){p[a]!==!c&&(c?(p[a]&&n--,n||(h(!0),this.$valid=!0,this.$invalid=!1)):(h(!1),this.$invalid=!0,this.$valid=!1,n++),p[a]=!c,h(c,a),l.$setValidity(a,c,this))};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;f.removeClass(e,yb);f.addClass(e,Ma)};this.$setViewValue=function(d){this.$viewValue=d;this.$pristine&&(this.$dirty=!0,this.$pristine=!1,f.removeClass(e,Ma),f.addClass(e,yb),l.$setDirty());q(this.$parsers,function(a){d=
|
|
||||||
a(d)});this.$modelValue!==d&&(this.$modelValue=d,k(a,d),q(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}}))};var r=this;a.$watch(function(){var c=m(a);if(r.$modelValue!==c){var d=r.$formatters,e=d.length;for(r.$modelValue=c;e--;)c=d[e](c);r.$viewValue!==c&&(r.$viewValue=c,r.$render())}return c})}],Od=function(){return{require:["ngModel","^?form"],controller:af,link:function(a,c,d,e){var g=e[0],f=e[1]||xb;f.$addControl(g);a.$on("$destroy",function(){f.$removeControl(g)})}}},Qd=aa({require:"ngModel",
|
|
||||||
link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),hc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&e.$isEmpty(a))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},Pd=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||
|
|
||||||
d.ngList||",";e.$parsers.push(function(a){if(!D(a)){var c=[];a&&q(a.split(g),function(a){a&&c.push(ca(a))});return c}});e.$formatters.push(function(a){return M(a)?a.join(", "):s});e.$isEmpty=function(a){return!a||!a.length}}}},bf=/^(true|false|\d+)$/,Rd=function(){return{priority:100,compile:function(a,c){return bf.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a)})}}}},rd=xa(function(a,c,d){c.addClass("ng-binding").data("$binding",
|
|
||||||
d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==s?"":a)})}),td=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],sd=["$sce","$parse",function(a,c){return function(d,e,g){e.addClass("ng-binding").data("$binding",g.ngBindHtml);var f=c(g.ngBindHtml);d.$watch(function(){return(f(d)||"").toString()},function(c){e.html(a.getTrustedHtml(f(d))||"")})}}],ud=Rb("",!0),wd=
|
|
||||||
Rb("Odd",0),vd=Rb("Even",1),xd=xa({compile:function(a,c){c.$set("ngCloak",s);a.removeClass("ng-cloak")}}),yd=[function(){return{scope:!0,controller:"@",priority:500}}],ic={};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=na("ng-"+a);ic[c]=["$parse",function(d){return{compile:function(e,g){var f=d(g[c]);return function(c,d,e){d.on(I(a),function(a){c.$apply(function(){f(c,{$event:a})})})}}}}]});
|
|
||||||
var Bd=["$animate",function(a){return{transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,g,f){var h,m,k;c.$watch(e.ngIf,function(g){Pa(g)?m||(m=c.$new(),f(m,function(c){c[c.length++]=U.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)})):(k&&(k.remove(),k=null),m&&(m.$destroy(),m=null),h&&(k=Db(h.clone),a.leave(k,function(){k=null}),h=null))})}}}],Cd=["$http","$templateCache","$anchorScroll","$animate","$sce",function(a,c,d,e,g){return{restrict:"ECA",
|
|
||||||
priority:400,terminal:!0,transclude:"element",controller:Qa.noop,compile:function(f,h){var m=h.ngInclude||h.src,k=h.onload||"",l=h.autoscroll;return function(f,h,r,q,z){var s=0,w,y,G,x=function(){y&&(y.remove(),y=null);w&&(w.$destroy(),w=null);G&&(e.leave(G,function(){y=null}),y=G,G=null)};f.$watch(g.parseAsResourceUrl(m),function(g){var m=function(){!B(l)||l&&!f.$eval(l)||d()},r=++s;g?(a.get(g,{cache:c}).success(function(a){if(r===s){var c=f.$new();q.template=a;a=z(c,function(a){x();e.enter(a,null,
|
|
||||||
h,m)});w=c;G=a;w.$emit("$includeContentLoaded");f.$eval(k)}}).error(function(){r===s&&x()}),f.$emit("$includeContentRequested")):(x(),q.template=null)})}}}}],Sd=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,g){d.html(g.template);a(d.contents())(c)}}}],Dd=xa({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Ed=xa({terminal:!0,priority:1E3}),Fd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",
|
|
||||||
link:function(e,g,f){var h=f.count,m=f.$attr.when&&g.attr(f.$attr.when),k=f.offset||0,l=e.$eval(m)||{},n={},p=c.startSymbol(),r=c.endSymbol(),s=/^when(Minus)?(.+)$/;q(f,function(a,c){s.test(c)&&(l[I(c.replace("when","").replace("Minus","-"))]=g.attr(f.$attr[c]))});q(l,function(a,e){n[e]=c(a.replace(d,p+h+"-"+k+r))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in l||(c=a.pluralCat(c-k));return n[c](e,g,!0)},function(a){g.text(a)})}}}],Gd=["$parse","$animate",function(a,
|
|
||||||
c){var d=v("ngRepeat");return{transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,link:function(e,g,f,h,m){var k=f.ngRepeat,l=k.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),n,p,r,s,z,B,w={$id:Ja};if(!l)throw d("iexp",k);f=l[1];h=l[2];(l=l[3])?(n=a(l),p=function(a,c,d){B&&(w[B]=a);w[z]=c;w.$index=d;return n(e,w)}):(r=function(a,c){return Ja(c)},s=function(a){return a});l=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!l)throw d("iidexp",f);z=l[3]||l[1];
|
|
||||||
B=l[2];var H={};e.$watchCollection(h,function(a){var f,h,l=g[0],n,w={},E,R,t,C,S,v,D=[];if(db(a))S=a,n=p||r;else{n=p||s;S=[];for(t in a)a.hasOwnProperty(t)&&"$"!=t.charAt(0)&&S.push(t);S.sort()}E=S.length;h=D.length=S.length;for(f=0;f<h;f++)if(t=a===S?f:S[f],C=a[t],C=n(t,C,f),Ba(C,"`track by` id"),H.hasOwnProperty(C))v=H[C],delete H[C],w[C]=v,D[f]=v;else{if(w.hasOwnProperty(C))throw q(D,function(a){a&&a.scope&&(H[a.id]=a)}),d("dupes",k,C);D[f]={id:C};w[C]=!1}for(t in H)H.hasOwnProperty(t)&&(v=H[t],
|
|
||||||
f=Db(v.clone),c.leave(f),q(f,function(a){a.$$NG_REMOVED=!0}),v.scope.$destroy());f=0;for(h=S.length;f<h;f++){t=a===S?f:S[f];C=a[t];v=D[f];D[f-1]&&(l=D[f-1].clone[D[f-1].clone.length-1]);if(v.scope){R=v.scope;n=l;do n=n.nextSibling;while(n&&n.$$NG_REMOVED);v.clone[0]!=n&&c.move(Db(v.clone),null,y(l));l=v.clone[v.clone.length-1]}else R=e.$new();R[z]=C;B&&(R[B]=t);R.$index=f;R.$first=0===f;R.$last=f===E-1;R.$middle=!(R.$first||R.$last);R.$odd=!(R.$even=0===(f&1));v.scope||m(R,function(a){a[a.length++]=
|
|
||||||
U.createComment(" end ngRepeat: "+k+" ");c.enter(a,null,y(l));l=a;v.scope=R;v.clone=a;w[v.id]=v})}H=w})}}}],Hd=["$animate",function(a){return function(c,d,e){c.$watch(e.ngShow,function(c){a[Pa(c)?"removeClass":"addClass"](d,"ng-hide")})}}],Ad=["$animate",function(a){return function(c,d,e){c.$watch(e.ngHide,function(c){a[Pa(c)?"addClass":"removeClass"](d,"ng-hide")})}}],Id=xa(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&q(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Jd=["$animate",
|
|
||||||
function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,g){var f,h,m,k=[];c.$watch(e.ngSwitch||e.on,function(d){var n,p=k.length;if(0<p){if(m){for(n=0;n<p;n++)m[n].remove();m=null}m=[];for(n=0;n<p;n++){var r=h[n];k[n].$destroy();m[n]=r;a.leave(r,function(){m.splice(n,1);0===m.length&&(m=null)})}}h=[];k=[];if(f=g.cases["!"+d]||g.cases["?"])c.$eval(e.change),q(f,function(d){var e=c.$new();k.push(e);d.transclude(e,function(c){var e=d.element;
|
|
||||||
h.push(c);a.enter(c,e.parent(),e)})})})}}}],Kd=xa({transclude:"element",priority:800,require:"^ngSwitch",link:function(a,c,d,e,g){e.cases["!"+d.ngSwitchWhen]=e.cases["!"+d.ngSwitchWhen]||[];e.cases["!"+d.ngSwitchWhen].push({transclude:g,element:c})}}),Ld=xa({transclude:"element",priority:800,require:"^ngSwitch",link:function(a,c,d,e,g){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:g,element:c})}}),Nd=xa({link:function(a,c,d,e,g){if(!g)throw v("ngTransclude")("orphan",ha(c));g(function(a){c.empty();
|
|
||||||
c.append(a)})}}),nd=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],cf=v("ngOptions"),Md=aa({terminal:!0}),od=["$compile","$parse",function(a,c){var d=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,e={$setViewValue:C};return{restrict:"E",require:["select","?ngModel"],
|
|
||||||
controller:["$element","$scope","$attrs",function(a,c,d){var m=this,k={},l=e,n;m.databound=d.ngModel;m.init=function(a,c,d){l=a;n=d};m.addOption=function(c){Ba(c,'"option value"');k[c]=!0;l.$viewValue==c&&(a.val(c),n.parent()&&n.remove())};m.removeOption=function(a){this.hasOption(a)&&(delete k[a],l.$viewValue==a&&this.renderUnknownOption(a))};m.renderUnknownOption=function(c){c="? "+Ja(c)+" ?";n.val(c);a.prepend(n);a.val(c);n.prop("selected",!0)};m.hasOption=function(a){return k.hasOwnProperty(a)};
|
|
||||||
c.$on("$destroy",function(){m.renderUnknownOption=C})}],link:function(e,f,h,m){function k(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(G.parent()&&G.remove(),c.val(a),""===a&&v.prop("selected",!0)):D(a)&&v?c.val(""):e.renderUnknownOption(a)};c.on("change",function(){a.$apply(function(){G.parent()&&G.remove();d.$setViewValue(c.val())})})}function l(a,c,d){var e;d.$render=function(){var a=new Wa(d.$viewValue);q(c.find("option"),function(c){c.selected=B(a.get(c.value))})};a.$watch(function(){za(e,
|
|
||||||
d.$viewValue)||(e=ba(d.$viewValue),d.$render())});c.on("change",function(){a.$apply(function(){var a=[];q(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function n(e,f,g){function h(){var a={"":[]},c=[""],d,k,s,t,u;t=g.$modelValue;u=y(e)||[];var D=n?Tb(u):u,G,J,A;J={};s=!1;var E,I;if(r)if(v&&M(t))for(s=new Wa([]),A=0;A<t.length;A++)J[l]=t[A],s.put(v(e,J),t[A]);else s=new Wa(t);for(A=0;G=D.length,A<G;A++){k=A;if(n){k=D[A];if("$"===k.charAt(0))continue;J[n]=k}J[l]=
|
|
||||||
u[k];d=p(e,J)||"";(k=a[d])||(k=a[d]=[],c.push(d));r?d=B(s.remove(v?v(e,J):q(e,J))):(v?(d={},d[l]=t,d=v(e,d)===v(e,J)):d=t===q(e,J),s=s||d);E=m(e,J);E=B(E)?E:"";k.push({id:v?v(e,J):n?D[A]:A,label:E,selected:d})}r||(z||null===t?a[""].unshift({id:"",label:"",selected:!s}):s||a[""].unshift({id:"?",label:"",selected:!0}));J=0;for(D=c.length;J<D;J++){d=c[J];k=a[d];x.length<=J?(t={element:C.clone().attr("label",d),label:k.label},u=[t],x.push(u),f.append(t.element)):(u=x[J],t=u[0],t.label!=d&&t.element.attr("label",
|
|
||||||
t.label=d));E=null;A=0;for(G=k.length;A<G;A++)s=k[A],(d=u[A+1])?(E=d.element,d.label!==s.label&&E.text(d.label=s.label),d.id!==s.id&&E.val(d.id=s.id),d.selected!==s.selected&&E.prop("selected",d.selected=s.selected)):(""===s.id&&z?I=z:(I=w.clone()).val(s.id).attr("selected",s.selected).text(s.label),u.push({element:I,label:s.label,id:s.id,selected:s.selected}),E?E.after(I):t.element.append(I),E=I);for(A++;u.length>A;)u.pop().element.remove()}for(;x.length>J;)x.pop()[0].element.remove()}var k;if(!(k=
|
|
||||||
t.match(d)))throw cf("iexp",t,ha(f));var m=c(k[2]||k[1]),l=k[4]||k[6],n=k[5],p=c(k[3]||""),q=c(k[2]?k[1]:l),y=c(k[7]),v=k[8]?c(k[8]):null,x=[[{element:f,label:""}]];z&&(a(z)(e),z.removeClass("ng-scope"),z.remove());f.empty();f.on("change",function(){e.$apply(function(){var a,c=y(e)||[],d={},h,k,m,p,t,w,u;if(r)for(k=[],p=0,w=x.length;p<w;p++)for(a=x[p],m=1,t=a.length;m<t;m++){if((h=a[m].element)[0].selected){h=h.val();n&&(d[n]=h);if(v)for(u=0;u<c.length&&(d[l]=c[u],v(e,d)!=h);u++);else d[l]=c[h];k.push(q(e,
|
|
||||||
d))}}else{h=f.val();if("?"==h)k=s;else if(""===h)k=null;else if(v)for(u=0;u<c.length;u++){if(d[l]=c[u],v(e,d)==h){k=q(e,d);break}}else d[l]=c[h],n&&(d[n]=h),k=q(e,d);1<x[0].length&&x[0][1].id!==h&&(x[0][1].selected=!1)}g.$setViewValue(k)})});g.$render=h;e.$watch(h)}if(m[1]){var p=m[0];m=m[1];var r=h.multiple,t=h.ngOptions,z=!1,v,w=y(U.createElement("option")),C=y(U.createElement("optgroup")),G=w.clone();h=0;for(var x=f.children(),A=x.length;h<A;h++)if(""===x[h].value){v=z=x.eq(h);break}p.init(m,z,
|
|
||||||
G);r&&(m.$isEmpty=function(a){return!a||0===a.length});t?n(e,f,m):r?l(e,f,m):k(e,f,m,p)}}}}],qd=["$interpolate",function(a){var c={addOption:C,removeOption:C};return{restrict:"E",priority:100,compile:function(d,e){if(D(e.value)){var g=a(d.text(),!0);g||e.$set("value",d.text())}return function(a,d,e){var k=d.parent(),l=k.data("$selectController")||k.parent().data("$selectController");l&&l.databound?d.prop("selected",!1):l=c;g?a.$watch(g,function(a,c){e.$set("value",a);a!==c&&l.removeOption(c);l.addOption(a)}):
|
|
||||||
l.addOption(e.value);d.on("$destroy",function(){l.removeOption(e.value)})}}}}],pd=aa({restrict:"E",terminal:!1});O.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):((Ha=O.jQuery)?(y=Ha,A(Ha.fn,{scope:Ka.scope,isolateScope:Ka.isolateScope,controller:Ka.controller,injector:Ka.injector,inheritedData:Ka.inheritedData}),Fb("remove",!0,!0,!1),Fb("empty",!1,!1,!1),Fb("html",!1,!1,!0)):y=N,Qa.element=y,hd(Qa),y(U).ready(function(){ed(U,cc)}))})(window,document);
|
|
||||||
!angular.$$csp()&&angular.element(document).find("head").prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}</style>');
|
|
||||||
//# sourceMappingURL=angular.min.js.map
|
|
|
@ -1 +0,0 @@
|
||||||
!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b){function c(a){return decodeURIComponent(atob(a).replace(/(.)/g,function(a,b){var c=b.charCodeAt(0).toString(16).toUpperCase();return c.length<2&&(c="0"+c),"%"+c}))}var d=a("Base64");b.exports=function(a){var b=a.replace(/-/g,"+").replace(/_/g,"/");switch(b.length%4){case 0:break;case 2:b+="==";break;case 3:b+="=";break;default:throw"Illegal base64url string!"}try{return c(b)}catch(e){return d.atob(b)}}},{Base64:4}],2:[function(a,b){"use strict";var c=a("./base64_url_decode"),d=a("./json_parse");b.exports=function(a){if(!a)throw new Error("Invalid token specified");return d(c(a.split(".")[1]))}},{"./base64_url_decode":1,"./json_parse":3}],3:[function(require,module,exports){module.exports=function(str){var parsed;return parsed="object"==typeof JSON?JSON.parse(str):eval("("+str+")")}},{}],4:[function(a,b,c){!function(){var a="undefined"!=typeof c?c:this,b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",d=function(){try{document.createElement("$")}catch(a){return a}}();a.btoa||(a.btoa=function(a){for(var c,e,f=0,g=b,h="";a.charAt(0|f)||(g="=",f%1);h+=g.charAt(63&c>>8-f%1*8)){if(e=a.charCodeAt(f+=.75),e>255)throw d;c=c<<8|e}return h}),a.atob||(a.atob=function(a){if(a=a.replace(/=+$/,""),a.length%4==1)throw d;for(var c,e,f=0,g=0,h="";e=a.charAt(g++);~e&&(c=f%4?64*c+e:e,f++%4)?h+=String.fromCharCode(255&c>>(-2*f&6)):0)e=b.indexOf(e);return h})}()},{}],5:[function(a){var b="undefined"!=typeof self?self:"undefined"!=typeof window?window:{},c=a("./lib/index");"function"==typeof b.window.define&&b.window.define.amd?b.window.define("jwt_decode",function(){return c}):b.window&&(b.window.jwt_decode=c)},{"./lib/index":2}]},{},[5]);
|
|
|
@ -1,19 +0,0 @@
|
||||||
<h1>All Albums</h1>
|
|
||||||
<table class="table" data-ng-repeat="(key, value) in albums">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{key}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<ul>
|
|
||||||
<li data-ng-repeat="p in value">
|
|
||||||
<a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<h1>Create an Album</h1>
|
|
||||||
|
|
||||||
<form>
|
|
||||||
Name: <input type="text" id="album.name" ng-model="album.name"/>
|
|
||||||
|
|
||||||
<button ng-click="create()" id="save-album">Save</button>
|
|
||||||
</form>
|
|
|
@ -1 +0,0 @@
|
||||||
<h1>{{album.name}}</h1>
|
|
|
@ -1,22 +0,0 @@
|
||||||
<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
|
|
||||||
<div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
|
|
||||||
<hr/>
|
|
||||||
<br/>
|
|
||||||
<div data-ng-show="!Identity.isAdmin()">
|
|
||||||
<a href="#/album/create" id="create-album">Create Album</a> | <a href="#/profile">My Profile</a>
|
|
||||||
<br/>
|
|
||||||
<h3>Your Albums</h3>
|
|
||||||
<span data-ng-show="albums.length == 0" id="resource-list-empty">You don't have any albums, yet.</span>
|
|
||||||
<table class="table" data-ng-show="albums.length > 0">
|
|
||||||
<tr data-ng-repeat="p in albums">
|
|
||||||
<td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<h3>Shared With Me</h3>
|
|
||||||
<span data-ng-show="shares.length == 0" id="share-list-empty">You don't have any shares, yet.</span>
|
|
||||||
<table class="table" data-ng-show="shares.length > 0">
|
|
||||||
<tr data-ng-repeat="p in shares">
|
|
||||||
<td><a id="view-share-{{p.album.name}}" href="#/album/{{p.album.id}}">{{p.album.name}}</a> - <a href="#" id="delete-share-{{p.album.name}}" data-ng-show="hasAccess(p, 'album:delete')" ng-click="deleteAlbum(p.album)">[X]</a><a href="#" id="request-delete-share-{{p.album.name}}" data-ng-hide="hasAccess(p, 'album:delete')" ng-click="requestDeleteAccess(p.album)">Request Delete Access</a></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<h1>My Profile</h1>
|
|
||||||
|
|
||||||
<form>
|
|
||||||
<p>Name: {{profile.userName}}</p>
|
|
||||||
<p>Total of albums: {{profile.totalAlbums}}</p>
|
|
||||||
</form>
|
|
|
@ -1,132 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "photoz",
|
|
||||||
"enabled": true,
|
|
||||||
"userManagedAccessAllowed": "true",
|
|
||||||
"sslRequired": "external",
|
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
|
||||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"requiredCredentials": [
|
|
||||||
"password"
|
|
||||||
],
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"username": "alice",
|
|
||||||
"enabled": true,
|
|
||||||
"email": "alice@keycloak.org",
|
|
||||||
"firstName": "Alice",
|
|
||||||
"lastName": "In Chains",
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"value": "alice"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"realmRoles": [
|
|
||||||
"user", "uma_authorization"
|
|
||||||
],
|
|
||||||
"clientRoles": {
|
|
||||||
"photoz-restful-api": [
|
|
||||||
"manage-albums"
|
|
||||||
],
|
|
||||||
"account": [
|
|
||||||
"manage-account"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "jdoe",
|
|
||||||
"enabled": true,
|
|
||||||
"email": "jdoe@keycloak.org",
|
|
||||||
"firstName": "John",
|
|
||||||
"lastName": "Doe",
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"value": "jdoe"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"realmRoles": [
|
|
||||||
"user", "uma_authorization"
|
|
||||||
],
|
|
||||||
"clientRoles": {
|
|
||||||
"photoz-restful-api": [
|
|
||||||
"manage-albums"
|
|
||||||
],
|
|
||||||
"account": [
|
|
||||||
"manage-account"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"enabled": true,
|
|
||||||
"email": "admin@admin.com",
|
|
||||||
"firstName": "Admin",
|
|
||||||
"lastName": "Istrator",
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"value": "admin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"realmRoles": [
|
|
||||||
"admin", "uma_authorization"
|
|
||||||
],
|
|
||||||
"clientRoles": {
|
|
||||||
"realm-management": [
|
|
||||||
"realm-admin"
|
|
||||||
],
|
|
||||||
"photoz-restful-api": [
|
|
||||||
"manage-albums"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "service-account-photoz-restful-api",
|
|
||||||
"enabled": true,
|
|
||||||
"email": "service-account-photoz-restful-api@placeholder.org",
|
|
||||||
"serviceAccountClientId": "photoz-restful-api",
|
|
||||||
"clientRoles": {
|
|
||||||
"photoz-restful-api" : ["uma_protection"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles": {
|
|
||||||
"realm": [
|
|
||||||
{
|
|
||||||
"name": "user",
|
|
||||||
"description": "User privileges"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "admin",
|
|
||||||
"description": "Administrator privileges"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"clients": [
|
|
||||||
{
|
|
||||||
"clientId": "photoz-html5-client",
|
|
||||||
"enabled": true,
|
|
||||||
"adminUrl": "http://localhost:8080/photoz-html5-client",
|
|
||||||
"baseUrl": "http://localhost:8080/photoz-html5-client",
|
|
||||||
"publicClient": true,
|
|
||||||
"consentRequired" : true,
|
|
||||||
"fullScopeAllowed" : true,
|
|
||||||
"redirectUris": [
|
|
||||||
"http://localhost:8080/photoz-html5-client/*"
|
|
||||||
],
|
|
||||||
"webOrigins": ["http://localhost:8080"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"clientId": "photoz-restful-api",
|
|
||||||
"secret": "secret",
|
|
||||||
"enabled": true,
|
|
||||||
"baseUrl": "http://localhost:8080/photoz-restful-api",
|
|
||||||
"authorizationServicesEnabled" : true,
|
|
||||||
"redirectUris": [
|
|
||||||
"http://localhost:8080/photoz-html5-client"
|
|
||||||
],
|
|
||||||
"webOrigins" : ["http://localhost:8080"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-photoz-parent</artifactId>
|
|
||||||
<version>4.0.0.CR1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>photoz-restful-api</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<name>Keycloak Authz: Photoz RESTful API</name>
|
|
||||||
<description>Photoz RESTful API</description>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
|
||||||
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.persistence</groupId>
|
|
||||||
<artifactId>persistence-api</artifactId>
|
|
||||||
<version>1.0.2</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.ejb</groupId>
|
|
||||||
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
|
|
||||||
<version>1.0.0.Final</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.enterprise</groupId>
|
|
||||||
<artifactId>cdi-api</artifactId>
|
|
||||||
<version>1.0-SP4</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-client</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>${project.artifactId}</finalName>
|
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>true</filtering>
|
|
||||||
</resource>
|
|
||||||
</resources>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,32 +0,0 @@
|
||||||
package org.keycloak.example.photoz;
|
|
||||||
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class ErrorResponse extends WebApplicationException {
|
|
||||||
|
|
||||||
private final Response.Status status;
|
|
||||||
|
|
||||||
public ErrorResponse(String message) {
|
|
||||||
this(message, Response.Status.INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorResponse(String message, Response.Status status) {
|
|
||||||
super(message, status);
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response getResponse() {
|
|
||||||
Map<String, String> errorResponse = new HashMap<>();
|
|
||||||
|
|
||||||
errorResponse.put("message", getMessage());
|
|
||||||
|
|
||||||
return Response.status(status).entity(errorResponse).build();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package org.keycloak.example.photoz;
|
|
||||||
|
|
||||||
import javax.ws.rs.ApplicationPath;
|
|
||||||
import javax.ws.rs.core.Application;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic auth app.
|
|
||||||
*/
|
|
||||||
@ApplicationPath("/")
|
|
||||||
public class PhotozApplication extends Application {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* JBoss, Home of Professional Open Source
|
|
||||||
*
|
|
||||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
|
||||||
*
|
|
||||||
* 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.keycloak.example.photoz.admin;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
|
|
||||||
import org.keycloak.example.photoz.entity.Album;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
@Path("/admin/album")
|
|
||||||
public class AdminAlbumService {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private EntityManager entityManager;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Produces("application/json")
|
|
||||||
public Response findAll() {
|
|
||||||
HashMap<String, List<Album>> albums = new HashMap<>();
|
|
||||||
List<Album> result = this.entityManager.createQuery("from Album").getResultList();
|
|
||||||
|
|
||||||
for (Album album : result) {
|
|
||||||
List<Album> userAlbums = albums.get(album.getUserId());
|
|
||||||
|
|
||||||
if (userAlbums == null) {
|
|
||||||
userAlbums = new ArrayList<>();
|
|
||||||
albums.put(album.getUserId(), userAlbums);
|
|
||||||
}
|
|
||||||
|
|
||||||
userAlbums.add(album);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.ok(albums).build();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,181 +0,0 @@
|
||||||
package org.keycloak.example.photoz.album;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.Query;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.ws.rs.Consumes;
|
|
||||||
import javax.ws.rs.DELETE;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.Response.Status;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
|
||||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
|
||||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
|
||||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
|
||||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
|
||||||
import org.keycloak.example.photoz.ErrorResponse;
|
|
||||||
import org.keycloak.example.photoz.entity.Album;
|
|
||||||
import org.keycloak.example.photoz.util.Transaction;
|
|
||||||
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
|
|
||||||
|
|
||||||
@Path("/album")
|
|
||||||
@Transaction
|
|
||||||
public class AlbumService {
|
|
||||||
|
|
||||||
public static final String SCOPE_ALBUM_VIEW = "album:view";
|
|
||||||
public static final String SCOPE_ALBUM_DELETE = "album:delete";
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private EntityManager entityManager;
|
|
||||||
|
|
||||||
@Context
|
|
||||||
private HttpServletRequest request;
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Consumes("application/json")
|
|
||||||
public Response create(Album newAlbum) {
|
|
||||||
Principal userPrincipal = request.getUserPrincipal();
|
|
||||||
|
|
||||||
newAlbum.setId(UUID.randomUUID().toString());
|
|
||||||
newAlbum.setUserId(userPrincipal.getName());
|
|
||||||
|
|
||||||
Query queryDuplicatedAlbum = this.entityManager.createQuery("from Album where name = :name and userId = :userId");
|
|
||||||
|
|
||||||
queryDuplicatedAlbum.setParameter("name", newAlbum.getName());
|
|
||||||
queryDuplicatedAlbum.setParameter("userId", userPrincipal.getName());
|
|
||||||
|
|
||||||
if (!queryDuplicatedAlbum.getResultList().isEmpty()) {
|
|
||||||
throw new ErrorResponse("Name [" + newAlbum.getName() + "] already taken. Choose another one.", Status.CONFLICT);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.entityManager.persist(newAlbum);
|
|
||||||
createProtectedResource(newAlbum);
|
|
||||||
} catch (Exception e) {
|
|
||||||
getAuthzClient().protection().resource().delete(newAlbum.getExternalId());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.ok(newAlbum).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Path("{id}")
|
|
||||||
@DELETE
|
|
||||||
public Response delete(@PathParam("id") String id) {
|
|
||||||
Album album = this.entityManager.find(Album.class, id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
deleteProtectedResource(album);
|
|
||||||
this.entityManager.remove(album);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Could not delete album.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.ok().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Produces("application/json")
|
|
||||||
public Response findAll() {
|
|
||||||
return Response.ok(this.entityManager.createQuery("from Album where userId = :id").setParameter("id", request.getUserPrincipal().getName()).getResultList()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/shares")
|
|
||||||
@Produces("application/json")
|
|
||||||
public Response findShares() {
|
|
||||||
List<PermissionTicketRepresentation> permissions = getAuthzClient().protection().permission().find(null, null, null, getKeycloakSecurityContext().getToken().getSubject(), true, true, null, null);
|
|
||||||
Map<String, SharedAlbum> shares = new HashMap<>();
|
|
||||||
|
|
||||||
for (PermissionTicketRepresentation permission : permissions) {
|
|
||||||
SharedAlbum share = shares.get(permission.getResource());
|
|
||||||
|
|
||||||
if (share == null) {
|
|
||||||
share = new SharedAlbum(Album.class.cast(entityManager.createQuery("from Album where externalId = :externalId").setParameter("externalId", permission.getResource()).getSingleResult()));
|
|
||||||
shares.put(permission.getResource(), share);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission.getScope() != null) {
|
|
||||||
share.addScope(permission.getScopeName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.ok(shares.values()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{id}")
|
|
||||||
@Produces("application/json")
|
|
||||||
public Response findById(@PathParam("id") String id) {
|
|
||||||
List result = this.entityManager.createQuery("from Album where id = :id").setParameter("id", id).getResultList();
|
|
||||||
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
return Response.status(Status.NOT_FOUND).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.ok(result.get(0)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createProtectedResource(Album album) {
|
|
||||||
try {
|
|
||||||
HashSet<ScopeRepresentation> scopes = new HashSet<>();
|
|
||||||
|
|
||||||
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
|
|
||||||
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
|
|
||||||
|
|
||||||
ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
|
|
||||||
|
|
||||||
albumResource.setOwner(album.getUserId());
|
|
||||||
albumResource.setOwnerManagedAccess(true);
|
|
||||||
|
|
||||||
ResourceRepresentation response = getAuthzClient().protection().resource().create(albumResource);
|
|
||||||
|
|
||||||
album.setExternalId(response.getId());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Could not register protected resource.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteProtectedResource(Album album) {
|
|
||||||
String uri = "/album/" + album.getId();
|
|
||||||
|
|
||||||
try {
|
|
||||||
ProtectionResource protection = getAuthzClient().protection();
|
|
||||||
List<ResourceRepresentation> search = protection.resource().findByUri(uri);
|
|
||||||
|
|
||||||
if (search.isEmpty()) {
|
|
||||||
throw new RuntimeException("Could not find protected resource with URI [" + uri + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
protection.resource().delete(search.get(0).getId());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Could not search protected resource.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AuthzClient getAuthzClient() {
|
|
||||||
return getAuthorizationContext().getClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClientAuthorizationContext getAuthorizationContext() {
|
|
||||||
return ClientAuthorizationContext.class.cast(getKeycloakSecurityContext().getAuthorizationContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeycloakSecurityContext getKeycloakSecurityContext() {
|
|
||||||
return KeycloakSecurityContext.class.cast(request.getAttribute(KeycloakSecurityContext.class.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* JBoss, Home of Professional Open Source
|
|
||||||
*
|
|
||||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
|
||||||
*
|
|
||||||
* 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.keycloak.example.photoz.album;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
@Path("/profile")
|
|
||||||
public class ProfileService {
|
|
||||||
|
|
||||||
private static final String PROFILE_VIEW = "urn:photoz.com:scopes:profile:view";
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private EntityManager entityManager;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Produces("application/json")
|
|
||||||
public Response view(@Context HttpServletRequest request) {
|
|
||||||
Principal userPrincipal = request.getUserPrincipal();
|
|
||||||
List albums = this.entityManager.createQuery("from Album where userId = :id").setParameter("id", userPrincipal.getName()).getResultList();
|
|
||||||
return Response.ok(new Profile(userPrincipal.getName(), albums.size())).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Profile {
|
|
||||||
private String userName;
|
|
||||||
private int totalAlbums;
|
|
||||||
|
|
||||||
public Profile(String name, int totalAlbums) {
|
|
||||||
this.userName = name;
|
|
||||||
this.totalAlbums = totalAlbums;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserName() {
|
|
||||||
return userName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTotalAlbums() {
|
|
||||||
return totalAlbums;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* 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.keycloak.example.photoz.album;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.keycloak.example.photoz.entity.Album;
|
|
||||||
|
|
||||||
public class SharedAlbum {
|
|
||||||
|
|
||||||
private Album album;
|
|
||||||
private List<String> scopes;
|
|
||||||
|
|
||||||
public SharedAlbum(Album album) {
|
|
||||||
this.album = album;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Album getAlbum() {
|
|
||||||
return album;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getScopes() {
|
|
||||||
return scopes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addScope(String scope) {
|
|
||||||
if (scopes == null) {
|
|
||||||
scopes = new ArrayList<>();
|
|
||||||
}
|
|
||||||
scopes.add(scope);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
* JBoss, Home of Professional Open Source
|
|
||||||
*
|
|
||||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
|
||||||
*
|
|
||||||
* 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.keycloak.example.photoz.entity;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.FetchType;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.OneToMany;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
public class Album {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "album", fetch = FetchType.EAGER)
|
|
||||||
private List<Photo> photos = new ArrayList<>();
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
@Column
|
|
||||||
private String externalId;
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(final String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(final String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Photo> getPhotos() {
|
|
||||||
return this.photos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhotos(final List<Photo> photos) {
|
|
||||||
this.photos = photos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserId(final String userId) {
|
|
||||||
this.userId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserId() {
|
|
||||||
return this.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExternalId(String externalId) {
|
|
||||||
this.externalId = externalId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExternalId() {
|
|
||||||
return externalId;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
* JBoss, Home of Professional Open Source
|
|
||||||
*
|
|
||||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
|
||||||
*
|
|
||||||
* 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.keycloak.example.photoz.entity;
|
|
||||||
|
|
||||||
import javax.persistence.Basic;
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.FetchType;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.Lob;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
public class Photo {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
private Album album;
|
|
||||||
|
|
||||||
@Lob
|
|
||||||
@Column
|
|
||||||
@Basic(fetch = FetchType.LAZY)
|
|
||||||
private byte[] image;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(final Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(final String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Album getAlbum() {
|
|
||||||
return this.album;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAlbum(final Album album) {
|
|
||||||
this.album = album;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getImage() {
|
|
||||||
return this.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setImage(final byte[] image) {
|
|
||||||
this.image = image;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* 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.keycloak.example.photoz.util;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.annotation.PreDestroy;
|
|
||||||
import javax.enterprise.context.ApplicationScoped;
|
|
||||||
import javax.enterprise.context.RequestScoped;
|
|
||||||
import javax.enterprise.inject.Produces;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.EntityManagerFactory;
|
|
||||||
import javax.persistence.Persistence;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
@ApplicationScoped
|
|
||||||
public class Resources {
|
|
||||||
|
|
||||||
private EntityManagerFactory entityManagerFactory;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
entityManagerFactory = Persistence.createEntityManagerFactory("primary");
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreDestroy
|
|
||||||
public void dispose() {
|
|
||||||
entityManagerFactory.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestScoped
|
|
||||||
@Produces
|
|
||||||
public EntityManager createEntityManager() {
|
|
||||||
return entityManagerFactory.createEntityManager();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* 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.keycloak.example.photoz.util;
|
|
||||||
|
|
||||||
import javax.interceptor.InterceptorBinding;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE;
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
@InterceptorBinding
|
|
||||||
@Target({ TYPE })
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
public @interface Transaction {
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* 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.keycloak.example.photoz.util;
|
|
||||||
|
|
||||||
import javax.enterprise.inject.Instance;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.interceptor.AroundInvoke;
|
|
||||||
import javax.interceptor.Interceptor;
|
|
||||||
import javax.interceptor.InvocationContext;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.EntityTransaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
@Interceptor
|
|
||||||
@Transaction
|
|
||||||
public class TransactionInterceptor {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private Instance<EntityManager> entityManager;
|
|
||||||
|
|
||||||
@AroundInvoke
|
|
||||||
public Object aroundInvoke(InvocationContext context) {
|
|
||||||
EntityManager entityManager = this.entityManager.get();
|
|
||||||
EntityTransaction transaction = entityManager.getTransaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
transaction.begin();
|
|
||||||
Object proceed = context.proceed();
|
|
||||||
transaction.commit();
|
|
||||||
return proceed;
|
|
||||||
} catch (Exception cause) {
|
|
||||||
if (transaction != null && transaction.isActive()) {
|
|
||||||
transaction.rollback();
|
|
||||||
}
|
|
||||||
throw new RuntimeException(cause);
|
|
||||||
} finally {
|
|
||||||
entityManager.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<persistence version="2.0"
|
|
||||||
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://java.sun.com/xml/ns/persistence
|
|
||||||
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
|
||||||
<persistence-unit name="primary" transaction-type="RESOURCE_LOCAL">
|
|
||||||
<provider>org.hibernate.ejb.HibernatePersistence</provider>
|
|
||||||
|
|
||||||
<class>org.keycloak.example.photoz.entity.Album</class>
|
|
||||||
<class>org.keycloak.example.photoz.entity.Photo</class>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
|
|
||||||
<property name="hibernate.connection.driver_class" value="org.h2.Driver" />
|
|
||||||
<property name="hibernate.connection.url" value="jdbc:h2:~/keycloak-photoz-example" />
|
|
||||||
<property name="hibernate.connection.user" value="sa" />
|
|
||||||
<property name="hibernate.flushMode" value="FLUSH_AUTO" />
|
|
||||||
<property name="hibernate.hbm2ddl.auto" value="update" />
|
|
||||||
<property name="hibernate.show_sql" value="false" />
|
|
||||||
</properties>
|
|
||||||
</persistence-unit>
|
|
||||||
</persistence>
|
|
|
@ -1,152 +0,0 @@
|
||||||
{
|
|
||||||
"allowRemoteResourceManagement": true,
|
|
||||||
"policyEnforcementMode": "ENFORCING",
|
|
||||||
"resources": [
|
|
||||||
{
|
|
||||||
"name": "Admin Resources",
|
|
||||||
"uri": "/admin/*",
|
|
||||||
"type": "http://photoz.com/admin",
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "admin:manage"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "User Profile Resource",
|
|
||||||
"uri": "/profile",
|
|
||||||
"type": "http://photoz.com/profile",
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "profile:view"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Album Resource",
|
|
||||||
"uri": "/album/*",
|
|
||||||
"type": "http://photoz.com/album",
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "album:delete"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "album:view"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"name": "Only Owner and Administrators Policy",
|
|
||||||
"description": "Defines that only the resource owner and administrators can do something",
|
|
||||||
"type": "aggregate",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "AFFIRMATIVE",
|
|
||||||
"config": {
|
|
||||||
"applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Administration Policy",
|
|
||||||
"description": "Defines that only administrators from a specific network address can do something.",
|
|
||||||
"type": "aggregate",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Only From @keycloak.org or Admin",
|
|
||||||
"description": "Defines that only users from @keycloak.org",
|
|
||||||
"type": "js",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRealmRole('admin') || email.endsWith('@keycloak.org')) {\n $evaluation.grant();\n}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Only Owner Policy",
|
|
||||||
"description": "Defines that only the resource owner is allowed to do something",
|
|
||||||
"type": "rules",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"mavenArtifactVersion": "${project.version}",
|
|
||||||
"mavenArtifactId": "photoz-authz-policy",
|
|
||||||
"sessionName": "MainOwnerSession",
|
|
||||||
"mavenArtifactGroupId": "org.keycloak",
|
|
||||||
"moduleName": "PhotozAuthzOwnerPolicy",
|
|
||||||
"scannerPeriod": "1",
|
|
||||||
"scannerPeriodUnit": "Hours"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Any Admin Policy",
|
|
||||||
"description": "Defines that adminsitrators can do something",
|
|
||||||
"type": "role",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"roles": "[{\"id\":\"admin\",\"required\":true}]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Only From a Specific Client Address",
|
|
||||||
"description": "Defines that only clients from a specific address can do something",
|
|
||||||
"type": "js",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n $evaluation.grant();\n}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Any User Policy",
|
|
||||||
"description": "Defines that only users from well known clients are allowed to access",
|
|
||||||
"type": "role",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"roles": "[{\"id\":\"user\",\"required\":false},{\"id\":\"photoz-restful-api/manage-albums\",\"required\":true}]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Admin Resource Permission",
|
|
||||||
"description": "General policy for any administrative resource.",
|
|
||||||
"type": "resource",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"defaultResourceType": "http://photoz.com/admin",
|
|
||||||
"applyPolicies": "[\"Administration Policy\"]",
|
|
||||||
"default": "true"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Album Resource Permission",
|
|
||||||
"description": "A default permission that defines access for any album resource",
|
|
||||||
"type": "scope",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"resources": "[\"Album Resource\"]",
|
|
||||||
"scopes": "[\"album:view\",\"album:delete\"]",
|
|
||||||
"applyPolicies": "[\"Only Owner and Administrators Policy\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "View User Permission",
|
|
||||||
"description": "Defines who is allowed to view an user profile",
|
|
||||||
"type": "scope",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"scopes": "[\"profile:view\"]",
|
|
||||||
"applyPolicies": "[\"Only From @keycloak.org or Admin\"]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<jboss-deployment-structure>
|
|
||||||
<deployment>
|
|
||||||
<dependencies>
|
|
||||||
<module name="org.keycloak.keycloak-authz-client" services="import"/>
|
|
||||||
<module name="com.h2database.h2" services="import"/>
|
|
||||||
</dependencies>
|
|
||||||
</deployment>
|
|
||||||
</jboss-deployment-structure>
|
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "photoz",
|
|
||||||
"auth-server-url": "http://localhost:8180/auth",
|
|
||||||
"ssl-required": "external",
|
|
||||||
"resource": "photoz-restful-api",
|
|
||||||
"bearer-only" : true,
|
|
||||||
"credentials": {
|
|
||||||
"secret": "secret"
|
|
||||||
},
|
|
||||||
"policy-enforcer": {
|
|
||||||
"enforcement-mode": "PERMISSIVE",
|
|
||||||
"user-managed-access": {},
|
|
||||||
"paths": [
|
|
||||||
{
|
|
||||||
"name" : "Album Resource",
|
|
||||||
"path" : "/album/{id}",
|
|
||||||
"methods" : [
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"scopes" : ["album:delete"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"scopes" : ["album:view"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name" : "Album Resource",
|
|
||||||
"path" : "/album/shares",
|
|
||||||
"enforcement-mode": "DISABLED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path" : "/profile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name" : "Admin Resources",
|
|
||||||
"path" : "/admin/*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
|
||||||
version="3.0">
|
|
||||||
|
|
||||||
<module-name>photoz-restful-api</module-name>
|
|
||||||
|
|
||||||
<security-constraint>
|
|
||||||
<web-resource-collection>
|
|
||||||
<web-resource-name>All Resources</web-resource-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</web-resource-collection>
|
|
||||||
<auth-constraint>
|
|
||||||
<role-name>user</role-name>
|
|
||||||
</auth-constraint>
|
|
||||||
</security-constraint>
|
|
||||||
|
|
||||||
<security-constraint>
|
|
||||||
<web-resource-collection>
|
|
||||||
<web-resource-name>All Resources</web-resource-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</web-resource-collection>
|
|
||||||
<auth-constraint>
|
|
||||||
<role-name>admin</role-name>
|
|
||||||
</auth-constraint>
|
|
||||||
</security-constraint>
|
|
||||||
|
|
||||||
<login-config>
|
|
||||||
<auth-method>KEYCLOAK</auth-method>
|
|
||||||
<realm-name>photoz</realm-name>
|
|
||||||
</login-config>
|
|
||||||
|
|
||||||
<security-role>
|
|
||||||
<role-name>admin</role-name>
|
|
||||||
</security-role>
|
|
||||||
|
|
||||||
<security-role>
|
|
||||||
<role-name>user</role-name>
|
|
||||||
</security-role>
|
|
||||||
</web-app>
|
|
|
@ -1,24 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-example-parent</artifactId>
|
|
||||||
<version>4.0.0.CR1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>keycloak-authz-photoz-parent</artifactId>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<name>Keycloak Authz: PhotoZ Example Application Parent</name>
|
|
||||||
<description>PhotoZ Example Application</description>
|
|
||||||
|
|
||||||
<modules>
|
|
||||||
<module>photoz-restful-api</module>
|
|
||||||
<module>photoz-html5-client</module>
|
|
||||||
<module>photoz-authz-policy</module>
|
|
||||||
</modules>
|
|
||||||
</project>
|
|
|
@ -1,30 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-examples-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>4.0.0.CR1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>keycloak-authz-example-parent</artifactId>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<name>Keycloak Authz: Examples Parent</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.target>1.7</maven.compiler.target>
|
|
||||||
<maven.compiler.source>1.7</maven.compiler.source>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<modules>
|
|
||||||
<module>photoz</module>
|
|
||||||
<module>servlet-authz</module>
|
|
||||||
<module>hello-world</module>
|
|
||||||
<module>hello-world-authz-service</module>
|
|
||||||
</modules>
|
|
||||||
</project>
|
|
|
@ -1,54 +0,0 @@
|
||||||
# About the Example Application
|
|
||||||
|
|
||||||
This is a simple Servlet-based application that will introduce you to some of the main concepts around Keycloak Authorization Services.
|
|
||||||
|
|
||||||
For this application, users can be regular users, premium users or administrators, where:
|
|
||||||
|
|
||||||
* Regular users have very limited access.
|
|
||||||
* Premium users have access to the *premium area*
|
|
||||||
* Administrators have access to the *administration area*
|
|
||||||
|
|
||||||
In Keycloak, all the paths being protected are resources on the server.
|
|
||||||
|
|
||||||
This application will also show you how to create a dynamic menu with the permissions granted to an user.
|
|
||||||
|
|
||||||
## Create the Example Realm and a Resource Server
|
|
||||||
|
|
||||||
Considering that your Keycloak Server is up and running, log in to the Keycloak Administration Console.
|
|
||||||
|
|
||||||
Now, create a new realm based on the following configuration file:
|
|
||||||
|
|
||||||
examples/authz/servlet-authz/servlet-authz-realm.json
|
|
||||||
|
|
||||||
That will import a pre-configured realm with everything you need to run this example. For more details about how to import a realm
|
|
||||||
into Keycloak, check the Keycloak's reference documentation.
|
|
||||||
|
|
||||||
After importing that file, you'll have a new realm called ``servlet-authz``.
|
|
||||||
|
|
||||||
Now, let's import another configuration using the Administration Console in order to configure the client application ``servlet-authz-app`` as a resource server with all resources, scopes, permissions and policies.
|
|
||||||
|
|
||||||
Click on ``Clients`` on the left side menu. Click on the ``servlet-authz-app`` on the client listing page. This will
|
|
||||||
open the ``Client Details`` page. Once there, click on the `Authorization` tab.
|
|
||||||
|
|
||||||
Click on the ``Select file`` button, which means you want to import a resource server configuration. Now select the file that is located at:
|
|
||||||
|
|
||||||
examples/authz/servlet-authz/servlet-authz-app-config.json
|
|
||||||
|
|
||||||
Now click ``Upload`` and the resource server will be updated accordingly.
|
|
||||||
|
|
||||||
## Deploy and Run the Example Applications
|
|
||||||
|
|
||||||
To deploy the example application, follow these steps:
|
|
||||||
|
|
||||||
cd examples/authz/servlet-authz
|
|
||||||
mvn clean package wildfly:deploy
|
|
||||||
|
|
||||||
Now, try to access the client application using the following URL:
|
|
||||||
|
|
||||||
http://localhost:8080/servlet-authz-app
|
|
||||||
|
|
||||||
If everything is correct, you will be redirect to Keycloak login page. You can login to the application with the following credentials:
|
|
||||||
|
|
||||||
* username: jdoe / password: jdoe
|
|
||||||
* username: alice / password: alice
|
|
||||||
* username: admin / password: admin
|
|
|
@ -1,54 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-example-parent</artifactId>
|
|
||||||
<version>4.0.0.CR1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>servlet-authz</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<name>Keycloak Authz: Examples - Servlet Authorization</name>
|
|
||||||
<description>Servlet Authorization</description>
|
|
||||||
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-client</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>${project.artifactId}</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,145 +0,0 @@
|
||||||
{
|
|
||||||
"allowRemoteResourceManagement": true,
|
|
||||||
"policyEnforcementMode": "ENFORCING",
|
|
||||||
"resources": [
|
|
||||||
{
|
|
||||||
"name": "Admin Resource",
|
|
||||||
"uri": "/protected/admin/*",
|
|
||||||
"type": "http://servlet-authz/protected/admin",
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "urn:servlet-authz:protected:admin:access"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Protected Resource",
|
|
||||||
"uri": "/*",
|
|
||||||
"type": "http://servlet-authz/protected/resource",
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "urn:servlet-authz:protected:resource:access"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Premium Resource",
|
|
||||||
"uri": "/protected/premium/*",
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "urn:servlet-authz:protected:premium:access"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Main Page",
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "urn:servlet-authz:page:main:actionForAdmin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "urn:servlet-authz:page:main:actionForUser"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "urn:servlet-authz:page:main:actionForPremiumUser"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"name": "Any Admin Policy",
|
|
||||||
"description": "Defines that adminsitrators can do something",
|
|
||||||
"type": "role",
|
|
||||||
"config": {
|
|
||||||
"roles": "[{\"id\":\"admin\"}]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Any User Policy",
|
|
||||||
"description": "Defines that any user can do something",
|
|
||||||
"type": "role",
|
|
||||||
"config": {
|
|
||||||
"roles": "[{\"id\":\"user\"}]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Only Premium User Policy",
|
|
||||||
"description": "Defines that only premium users can do something",
|
|
||||||
"type": "role",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"config": {
|
|
||||||
"roles": "[{\"id\":\"user_premium\"}]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "All Users Policy",
|
|
||||||
"description": "Defines that all users can do something",
|
|
||||||
"type": "aggregate",
|
|
||||||
"decisionStrategy": "AFFIRMATIVE",
|
|
||||||
"config": {
|
|
||||||
"applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Premium Resource Permission",
|
|
||||||
"description": "A policy that defines access to premium resources",
|
|
||||||
"type": "resource",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"resources": "[\"Premium Resource\"]",
|
|
||||||
"applyPolicies": "[\"Only Premium User Policy\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Administrative Resource Permission",
|
|
||||||
"description": "A policy that defines access to administrative resources",
|
|
||||||
"type": "resource",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"resources": "[\"Admin Resource\"]",
|
|
||||||
"applyPolicies": "[\"Any Admin Policy\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Protected Resource Permission",
|
|
||||||
"description": "A policy that defines access to any protected resource",
|
|
||||||
"type": "resource",
|
|
||||||
"decisionStrategy": "AFFIRMATIVE",
|
|
||||||
"config": {
|
|
||||||
"resources": "[\"Protected Resource\"]",
|
|
||||||
"applyPolicies": "[\"All Users Policy\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Action 1 on Main Page Resource Permission",
|
|
||||||
"description": "A policy that defines access to action 1 on the main page",
|
|
||||||
"type": "scope",
|
|
||||||
"decisionStrategy": "AFFIRMATIVE",
|
|
||||||
"config": {
|
|
||||||
"scopes": "[\"urn:servlet-authz:page:main:actionForAdmin\"]",
|
|
||||||
"applyPolicies": "[\"Any Admin Policy\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Action 2 on Main Page Resource Permission",
|
|
||||||
"description": "A policy that defines access to action 2 on the main page",
|
|
||||||
"type": "scope",
|
|
||||||
"decisionStrategy": "AFFIRMATIVE",
|
|
||||||
"config": {
|
|
||||||
"scopes": "[\"urn:servlet-authz:page:main:actionForUser\"]",
|
|
||||||
"applyPolicies": "[\"Any User Policy\"]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Action 3 on Main Page Resource Permission",
|
|
||||||
"description": "A policy that defines access to action 3 on the main page",
|
|
||||||
"type": "scope",
|
|
||||||
"decisionStrategy": "AFFIRMATIVE",
|
|
||||||
"config": {
|
|
||||||
"scopes": "[\"urn:servlet-authz:page:main:actionForPremiumUser\"]",
|
|
||||||
"applyPolicies": "[\"Only Premium User Policy\"]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "servlet-authz",
|
|
||||||
"enabled": true,
|
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
|
||||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"requiredCredentials": [
|
|
||||||
"password"
|
|
||||||
],
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"username": "alice",
|
|
||||||
"enabled": true,
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"value": "alice"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"realmRoles": [
|
|
||||||
"user"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "jdoe",
|
|
||||||
"enabled": true,
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"value": "jdoe"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"realmRoles": [
|
|
||||||
"user",
|
|
||||||
"user_premium"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"enabled": true,
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"value": "admin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"realmRoles": [
|
|
||||||
"user",
|
|
||||||
"admin"
|
|
||||||
],
|
|
||||||
"clientRoles": {
|
|
||||||
"realm-management": [
|
|
||||||
"realm-admin"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "service-account-servlet-authz-app",
|
|
||||||
"enabled": true,
|
|
||||||
"serviceAccountClientId": "servlet-authz-app",
|
|
||||||
"clientRoles": {
|
|
||||||
"servlet-authz-app" : ["uma_protection"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles": {
|
|
||||||
"realm": [
|
|
||||||
{
|
|
||||||
"name": "user",
|
|
||||||
"description": "User privileges"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "admin",
|
|
||||||
"description": "Administrator privileges"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user_premium",
|
|
||||||
"description": "User Premium privileges"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"clients": [
|
|
||||||
{
|
|
||||||
"clientId": "servlet-authz-app",
|
|
||||||
"enabled": true,
|
|
||||||
"baseUrl": "/servlet-authz-app",
|
|
||||||
"adminUrl": "/servlet-authz-app",
|
|
||||||
"bearerOnly": false,
|
|
||||||
"authorizationServicesEnabled": true,
|
|
||||||
"redirectUris": [
|
|
||||||
"/servlet-authz-app/*"
|
|
||||||
],
|
|
||||||
"secret": "secret"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<jboss-deployment-structure>
|
|
||||||
<deployment>
|
|
||||||
<dependencies>
|
|
||||||
<module name="org.keycloak.keycloak-authz-client" services="import"/>
|
|
||||||
</dependencies>
|
|
||||||
</deployment>
|
|
||||||
</jboss-deployment-structure>
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "servlet-authz",
|
|
||||||
"auth-server-url": "http://localhost:8180/auth",
|
|
||||||
"ssl-required": "external",
|
|
||||||
"resource": "servlet-authz-app",
|
|
||||||
"credentials": {
|
|
||||||
"secret": "secret"
|
|
||||||
},
|
|
||||||
"policy-enforcer": {}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
|
||||||
version="3.0">
|
|
||||||
|
|
||||||
<module-name>servlet-authz-app</module-name>
|
|
||||||
|
|
||||||
<security-constraint>
|
|
||||||
<web-resource-collection>
|
|
||||||
<web-resource-name>All Resources</web-resource-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</web-resource-collection>
|
|
||||||
<auth-constraint>
|
|
||||||
<role-name>user</role-name>
|
|
||||||
<role-name>admin</role-name>
|
|
||||||
<role-name>user_premium</role-name>
|
|
||||||
</auth-constraint>
|
|
||||||
</security-constraint>
|
|
||||||
|
|
||||||
<login-config>
|
|
||||||
<auth-method>KEYCLOAK</auth-method>
|
|
||||||
<realm-name>servlet-authz</realm-name>
|
|
||||||
</login-config>
|
|
||||||
|
|
||||||
<security-role>
|
|
||||||
<role-name>admin</role-name>
|
|
||||||
</security-role>
|
|
||||||
|
|
||||||
<security-role>
|
|
||||||
<role-name>user</role-name>
|
|
||||||
</security-role>
|
|
||||||
|
|
||||||
<security-role>
|
|
||||||
<role-name>user_premium</role-name>
|
|
||||||
</security-role>
|
|
||||||
|
|
||||||
<error-page>
|
|
||||||
<error-code>403</error-code>
|
|
||||||
<location>/accessDenied.jsp</location>
|
|
||||||
</error-page>
|
|
||||||
|
|
||||||
</web-app>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2 style="color: red">You can not access this resource.</h2>
|
|
||||||
<%@include file="logout-include.jsp"%>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<%@page import="org.keycloak.AuthorizationContext" %>
|
|
||||||
<%@ page import="org.keycloak.KeycloakSecurityContext" %>
|
|
||||||
<%@ page import="org.keycloak.representations.idm.authorization.Permission" %>
|
|
||||||
|
|
||||||
<%
|
|
||||||
KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
|
|
||||||
AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
|
|
||||||
%>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<%@include file="logout-include.jsp"%>
|
|
||||||
<h2>This is a public resource. Try to access one of these <i>protected</i> resources:</h2>
|
|
||||||
|
|
||||||
<p><a href="protected/dynamicMenu.jsp">Dynamic Menu</a></p>
|
|
||||||
<p><a href="protected/premium/onlyPremium.jsp">User Premium</a></p>
|
|
||||||
<p><a href="protected/admin/onlyAdmin.jsp">Administration</a></p>
|
|
||||||
|
|
||||||
<h3>Your permissions are:</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<%
|
|
||||||
for (Permission permission : authzContext.getPermissions()) {
|
|
||||||
%>
|
|
||||||
<li>
|
|
||||||
<p>Resource: <%= permission.getResourceName() %></p>
|
|
||||||
<p>ID: <%= permission.getResourceId() %></p>
|
|
||||||
<p>Scopes: <%= permission.getScopes() %></p>
|
|
||||||
</li>
|
|
||||||
<%
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
|
|
||||||
<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
|
|
||||||
<%
|
|
||||||
String scheme = request.getScheme();
|
|
||||||
String host = request.getServerName();
|
|
||||||
int port = request.getServerPort();
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
String redirectUri = scheme + "://" + host + ":" + port + contextPath;
|
|
||||||
%>
|
|
||||||
<h2>Click here <a href="<%= KeycloakUriBuilder.fromUri("http://localhost:8180/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
|
||||||
.queryParam("redirect_uri", redirectUri).build("servlet-authz").toString()%>">Sign Out</a></h2>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2>Only Administrators can access this page.</h2>
|
|
||||||
<%@include file="../../logout-include.jsp"%>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,48 +0,0 @@
|
||||||
<%@page import="org.keycloak.AuthorizationContext" %>
|
|
||||||
<%@ page import="org.keycloak.KeycloakSecurityContext" %>
|
|
||||||
|
|
||||||
<%
|
|
||||||
KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
|
|
||||||
AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
|
|
||||||
%>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2>Any authenticated user can access this page.</h2>
|
|
||||||
<%@include file="../logout-include.jsp"%>
|
|
||||||
|
|
||||||
<p>Here is a dynamic menu built from the permissions returned by the server:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<%
|
|
||||||
if (authzContext.hasResourcePermission("Protected Resource")) {
|
|
||||||
%>
|
|
||||||
<li>
|
|
||||||
Do user thing
|
|
||||||
</li>
|
|
||||||
<%
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
|
|
||||||
<%
|
|
||||||
if (authzContext.hasResourcePermission("Premium Resource")) {
|
|
||||||
%>
|
|
||||||
<li>
|
|
||||||
Do user premium thing
|
|
||||||
</li>
|
|
||||||
<%
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
|
|
||||||
<%
|
|
||||||
if (authzContext.hasPermission("Admin Resource", "urn:servlet-authz:protected:admin:access")) {
|
|
||||||
%>
|
|
||||||
<li>
|
|
||||||
Do administration thing
|
|
||||||
</li>
|
|
||||||
<%
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2>Only for premium users.</h2>
|
|
||||||
<%@include file="../../logout-include.jsp"%>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -66,6 +66,5 @@
|
||||||
<module>themes</module>
|
<module>themes</module>
|
||||||
<module>saml</module>
|
<module>saml</module>
|
||||||
<module>ldap</module>
|
<module>ldap</module>
|
||||||
<module>authz</module>
|
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -59,6 +59,11 @@ public interface ResourcesResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
List<ResourceRepresentation> findByName(@QueryParam("name") String name);
|
List<ResourceRepresentation> findByName(@QueryParam("name") String name);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
List<ResourceRepresentation> findByName(@QueryParam("name") String name, @QueryParam("owner") String owner);
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.connections.infinispan;
|
package org.keycloak.connections.infinispan;
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.infinispan.manager.EmbeddedCacheManager;
|
import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,11 +27,13 @@ import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
|
public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
|
||||||
|
|
||||||
private final EmbeddedCacheManager cacheManager;
|
private final EmbeddedCacheManager cacheManager;
|
||||||
|
private final RemoteCacheProvider remoteCacheProvider;
|
||||||
private final String siteName;
|
private final String siteName;
|
||||||
private final String nodeName;
|
private final String nodeName;
|
||||||
|
|
||||||
public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager, String nodeName, String siteName) {
|
public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager, RemoteCacheProvider remoteCacheProvider, String nodeName, String siteName) {
|
||||||
this.cacheManager = cacheManager;
|
this.cacheManager = cacheManager;
|
||||||
|
this.remoteCacheProvider = remoteCacheProvider;
|
||||||
this.nodeName = nodeName;
|
this.nodeName = nodeName;
|
||||||
this.siteName = siteName;
|
this.siteName = siteName;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +43,11 @@ public class DefaultInfinispanConnectionProvider implements InfinispanConnection
|
||||||
return cacheManager.getCache(name);
|
return cacheManager.getCache(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <K, V> RemoteCache<K, V> getRemoteCache(String cacheName) {
|
||||||
|
return remoteCacheProvider.getRemoteCache(cacheName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getNodeName() {
|
public String getNodeName() {
|
||||||
return nodeName;
|
return nodeName;
|
||||||
|
|
|
@ -56,6 +56,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
|
|
||||||
protected EmbeddedCacheManager cacheManager;
|
protected EmbeddedCacheManager cacheManager;
|
||||||
|
|
||||||
|
protected RemoteCacheProvider remoteCacheProvider;
|
||||||
|
|
||||||
protected boolean containerManaged;
|
protected boolean containerManaged;
|
||||||
|
|
||||||
private String nodeName;
|
private String nodeName;
|
||||||
|
@ -66,7 +68,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
public InfinispanConnectionProvider create(KeycloakSession session) {
|
public InfinispanConnectionProvider create(KeycloakSession session) {
|
||||||
lazyInit();
|
lazyInit();
|
||||||
|
|
||||||
return new DefaultInfinispanConnectionProvider(cacheManager, nodeName, siteName);
|
return new DefaultInfinispanConnectionProvider(cacheManager, remoteCacheProvider, nodeName, siteName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,6 +76,9 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
if (cacheManager != null && !containerManaged) {
|
if (cacheManager != null && !containerManaged) {
|
||||||
cacheManager.stop();
|
cacheManager.stop();
|
||||||
}
|
}
|
||||||
|
if (remoteCacheProvider != null) {
|
||||||
|
remoteCacheProvider.stop();
|
||||||
|
}
|
||||||
cacheManager = null;
|
cacheManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +109,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.infof("Node name: %s, Site name: %s", nodeName, siteName);
|
logger.infof("Node name: %s, Site name: %s", nodeName, siteName);
|
||||||
|
|
||||||
|
remoteCacheProvider = new RemoteCacheProvider(config, cacheManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.connections.infinispan;
|
package org.keycloak.connections.infinispan;
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,6 +68,12 @@ public interface InfinispanConnectionProvider extends Provider {
|
||||||
|
|
||||||
<K, V> Cache<K, V> getCache(String name);
|
<K, V> Cache<K, V> getCache(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get remote cache of given name. Could just retrieve the remote cache from the remoteStore configured in given infinispan cache and/or
|
||||||
|
* alternatively return the secured remoteCache (remoteCache corresponding to secured hotrod endpoint)
|
||||||
|
*/
|
||||||
|
<K, V> RemoteCache<K, V> getRemoteCache(String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Address of current node in cluster. In non-cluster environment, it returns some other non-null value (eg. hostname with some random value like "host-123456" )
|
* @return Address of current node in cluster. In non-cluster environment, it returns some other non-null value (eg. hostname with some random value like "host-123456" )
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.keycloak.connections.infinispan;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.security.auth.callback.Callback;
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
import javax.security.auth.callback.NameCallback;
|
||||||
|
import javax.security.auth.callback.PasswordCallback;
|
||||||
|
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||||
|
import javax.security.sasl.RealmCallback;
|
||||||
|
|
||||||
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
|
import org.infinispan.client.hotrod.RemoteCacheManager;
|
||||||
|
import org.infinispan.client.hotrod.configuration.Configuration;
|
||||||
|
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
|
||||||
|
import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.util.reflections.Reflections;
|
||||||
|
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get either just remoteCache associated with remoteStore associated with infinispan cache of given name. If security is enabled, then
|
||||||
|
* return secured remoteCache based on the template provided by remoteStore configuration but with added "authentication" configuration
|
||||||
|
* of secured hotrod endpoint (RemoteStore doesn't yet allow to configure "security" of hotrod endpoints)
|
||||||
|
*
|
||||||
|
* TODO: Remove this class once we upgrade to infinispan version, which allows to configure security for remoteStore itself
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class RemoteCacheProvider {
|
||||||
|
|
||||||
|
public static final String SCRIPT_CACHE_NAME = "___script_cache";
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(RemoteCacheProvider.class);
|
||||||
|
|
||||||
|
private final Config.Scope config;
|
||||||
|
private final EmbeddedCacheManager cacheManager;
|
||||||
|
|
||||||
|
private final Map<String, RemoteCache> availableCaches = new HashMap<>();
|
||||||
|
|
||||||
|
// Enlist secured managers, which are managed by us and should be shutdown on stop
|
||||||
|
private final Map<String, RemoteCacheManager> managedManagers = new HashMap<>();
|
||||||
|
|
||||||
|
public RemoteCacheProvider(Config.Scope config, EmbeddedCacheManager cacheManager) {
|
||||||
|
this.config = config;
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteCache getRemoteCache(String cacheName) {
|
||||||
|
if (availableCaches.get(cacheName) == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (availableCaches.get(cacheName) == null) {
|
||||||
|
RemoteCache remoteCache = loadRemoteCache(cacheName);
|
||||||
|
availableCaches.put(cacheName, remoteCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableCaches.get(cacheName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
logger.debugf("Shutdown %d registered secured remoteCache managers", managedManagers.size());
|
||||||
|
|
||||||
|
for (RemoteCacheManager mgr : managedManagers.values()) {
|
||||||
|
mgr.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected synchronized RemoteCache loadRemoteCache(String cacheName) {
|
||||||
|
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cacheManager.getCache(cacheName));
|
||||||
|
|
||||||
|
Boolean remoteStoreSecurity = config.getBoolean("remoteStoreSecurityEnabled");
|
||||||
|
if (remoteStoreSecurity == null) {
|
||||||
|
try {
|
||||||
|
logger.debugf("Detecting remote security settings of HotRod server, cache %s. Disable by explicitly setting \"remoteStoreSecurityEnabled\" property in spi=connectionsInfinispan/provider=default", cacheName);
|
||||||
|
remoteStoreSecurity = false;
|
||||||
|
final RemoteCache<Object, Object> scriptCache = remoteCache.getRemoteCacheManager().getCache(SCRIPT_CACHE_NAME);
|
||||||
|
if (scriptCache == null) {
|
||||||
|
logger.debug("Cannot detect remote security settings of HotRod server, disabling.");
|
||||||
|
} else {
|
||||||
|
scriptCache.containsKey("");
|
||||||
|
}
|
||||||
|
} catch (HotRodClientException ex) {
|
||||||
|
logger.debug("Seems that HotRod server requires authentication, enabling.");
|
||||||
|
remoteStoreSecurity = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteStoreSecurity) {
|
||||||
|
logger.infof("Remote store security for cache %s is enabled. Disable by setting \"remoteStoreSecurityEnabled\" property to \"false\" in spi=connectionsInfinispan/provider=default", cacheName);
|
||||||
|
RemoteCacheManager securedMgr = getOrCreateSecuredRemoteCacheManager(config, cacheName, remoteCache.getRemoteCacheManager());
|
||||||
|
return securedMgr.getCache(remoteCache.getName());
|
||||||
|
} else {
|
||||||
|
logger.infof("Remote store security for cache %s is disabled. If server fails to connect to remote JDG server, enable it.", cacheName);
|
||||||
|
return remoteCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected RemoteCacheManager getOrCreateSecuredRemoteCacheManager(Config.Scope config, String cacheName, RemoteCacheManager origManager) {
|
||||||
|
String serverName = config.get("remoteStoreSecurityServerName", "keycloak-jdg-server");
|
||||||
|
String realm = config.get("remoteStoreSecurityRealm", "AllowScriptManager");
|
||||||
|
|
||||||
|
String username = config.get("remoteStoreSecurityUsername", "___script_manager");
|
||||||
|
String password = config.get("remoteStoreSecurityPassword", "not-so-secret-password");
|
||||||
|
|
||||||
|
// Create configuration template from the original configuration provided at remoteStore level
|
||||||
|
Configuration origConfig = origManager.getConfiguration();
|
||||||
|
|
||||||
|
ConfigurationBuilder cfgBuilder = new ConfigurationBuilder()
|
||||||
|
.read(origConfig);
|
||||||
|
|
||||||
|
String securedHotRodEndpoint = origConfig.servers().stream()
|
||||||
|
.map(serverConfiguration -> serverConfiguration.host() + ":" + serverConfiguration.port())
|
||||||
|
.collect(Collectors.joining(";"));
|
||||||
|
|
||||||
|
if (managedManagers.containsKey(securedHotRodEndpoint)) {
|
||||||
|
return managedManagers.get(securedHotRodEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.infof("Creating secured RemoteCacheManager for Server: '%s', Cache: '%s', Realm: '%s', Username: '%s', Secured HotRod endpoint: '%s'", serverName, cacheName, realm, username, securedHotRodEndpoint);
|
||||||
|
|
||||||
|
// Workaround as I need a way to override servers and it's not possible to remove existing :/
|
||||||
|
try {
|
||||||
|
Field serversField = cfgBuilder.getClass().getDeclaredField("servers");
|
||||||
|
Reflections.setAccessible(serversField);
|
||||||
|
List origServers = Reflections.getFieldValue(serversField, cfgBuilder, List.class);
|
||||||
|
origServers.clear();
|
||||||
|
} catch (NoSuchFieldException nsfe) {
|
||||||
|
throw new RuntimeException(nsfe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create configuration based on the configuration template from remoteStore. Just add security and override secured endpoint
|
||||||
|
Configuration newConfig = cfgBuilder
|
||||||
|
.addServers(securedHotRodEndpoint)
|
||||||
|
.security()
|
||||||
|
.authentication()
|
||||||
|
.serverName(serverName) //define server name, should be specified in XML configuration on JDG side
|
||||||
|
.saslMechanism("DIGEST-MD5") // define SASL mechanism, in this example we use DIGEST with MD5 hash
|
||||||
|
.callbackHandler(new LoginHandler(username, password.toCharArray(), realm)) // define login handler, implementation defined
|
||||||
|
.enable()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final RemoteCacheManager remoteCacheManager = new RemoteCacheManager(newConfig);
|
||||||
|
managedManagers.put(securedHotRodEndpoint, remoteCacheManager);
|
||||||
|
return remoteCacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class LoginHandler implements CallbackHandler {
|
||||||
|
final private String login;
|
||||||
|
final private char[] password;
|
||||||
|
final private String realm;
|
||||||
|
|
||||||
|
private LoginHandler(String login, char[] password, String realm) {
|
||||||
|
this.login = login;
|
||||||
|
this.password = password;
|
||||||
|
this.realm = realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (Callback callback : callbacks) {
|
||||||
|
if (callback instanceof NameCallback) {
|
||||||
|
((NameCallback) callback).setName(login);
|
||||||
|
} else if (callback instanceof PasswordCallback) {
|
||||||
|
((PasswordCallback) callback).setPassword(password);
|
||||||
|
} else if (callback instanceof RealmCallback) {
|
||||||
|
((RealmCallback) callback).setText(realm);
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedCallbackException(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -79,7 +79,7 @@ public class StoreFactoryCacheManager extends CacheManager {
|
||||||
|
|
||||||
public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner, Set<String> invalidations) {
|
public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner, Set<String> invalidations) {
|
||||||
invalidations.add(id);
|
invalidations.add(id);
|
||||||
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId));
|
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, owner, serverId));
|
||||||
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
|
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
|
||||||
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, null));
|
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, null));
|
||||||
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResource(id, serverId));
|
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResource(id, serverId));
|
||||||
|
|
|
@ -336,8 +336,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
return "scope.name." + name + "." + serverId;
|
return "scope.name." + name + "." + serverId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getResourceByNameCacheKey(String name, String serverId) {
|
public static String getResourceByNameCacheKey(String name, String ownerId, String serverId) {
|
||||||
return "resource.name." + name + "." + serverId;
|
return "resource.name." + name + "." + ownerId + "." + serverId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getResourceByOwnerCacheKey(String owner, String serverId) {
|
public static String getResourceByOwnerCacheKey(String owner, String serverId) {
|
||||||
|
@ -580,17 +580,22 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Resource findByName(String name, String resourceServerId) {
|
public Resource findByName(String name, String resourceServerId) {
|
||||||
|
return findByName(name, resourceServerId, resourceServerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource findByName(String name, String ownerId, String resourceServerId) {
|
||||||
if (name == null) return null;
|
if (name == null) return null;
|
||||||
String cacheKey = getResourceByNameCacheKey(name, resourceServerId);
|
String cacheKey = getResourceByNameCacheKey(name, ownerId, resourceServerId);
|
||||||
List<Resource> result = cacheQuery(cacheKey, ResourceListQuery.class, () -> {
|
List<Resource> result = cacheQuery(cacheKey, ResourceListQuery.class, () -> {
|
||||||
Resource resource = getResourceStoreDelegate().findByName(name, resourceServerId);
|
Resource resource = getResourceStoreDelegate().findByName(name, ownerId, resourceServerId);
|
||||||
|
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Arrays.asList(resource);
|
return Arrays.asList(resource);
|
||||||
},
|
},
|
||||||
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||||
|
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
|
|
|
@ -125,7 +125,7 @@ public class UserSessionAdapter implements UserSessionModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAuthenticatedClientSessions(Collection<String> removedClientUUIDS) {
|
public void removeAuthenticatedClientSessions(Collection<String> removedClientUUIDS) {
|
||||||
if (removedClientUUIDS == null || ! removedClientUUIDS.isEmpty()) {
|
if (removedClientUUIDS == null || removedClientUUIDS.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,12 +27,12 @@ import org.infinispan.commons.marshall.Marshaller;
|
||||||
import org.infinispan.context.Flag;
|
import org.infinispan.context.Flag;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
import org.keycloak.connections.infinispan.RemoteCacheProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.BaseCacheInitializer;
|
import org.keycloak.models.sessions.infinispan.initializer.BaseCacheInitializer;
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
|
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.SessionLoader;
|
import org.keycloak.models.sessions.infinispan.initializer.SessionLoader;
|
||||||
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -87,9 +87,9 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(KeycloakSession session) {
|
public void init(KeycloakSession session) {
|
||||||
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(getCache(session));
|
RemoteCache remoteCache = getRemoteCache(session);
|
||||||
|
|
||||||
RemoteCache<String, String> scriptCache = remoteCache.getRemoteCacheManager().getCache("___script_cache");
|
RemoteCache<String, String> scriptCache = remoteCache.getRemoteCacheManager().getCache(RemoteCacheProvider.SCRIPT_CACHE_NAME);
|
||||||
|
|
||||||
if (!scriptCache.containsKey("load-sessions.js")) {
|
if (!scriptCache.containsKey("load-sessions.js")) {
|
||||||
scriptCache.put("load-sessions.js",
|
scriptCache.put("load-sessions.js",
|
||||||
|
@ -100,7 +100,7 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSessionsCount(KeycloakSession session) {
|
public int getSessionsCount(KeycloakSession session) {
|
||||||
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(getCache(session));
|
RemoteCache remoteCache = getRemoteCache(session);
|
||||||
return remoteCache.size();
|
return remoteCache.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
|
||||||
Cache cache = getCache(session);
|
Cache cache = getCache(session);
|
||||||
Cache decoratedCache = cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE, Flag.IGNORE_RETURN_VALUES);
|
Cache decoratedCache = cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE, Flag.IGNORE_RETURN_VALUES);
|
||||||
|
|
||||||
RemoteCache<?, ?> remoteCache = InfinispanUtil.getRemoteCache(cache);
|
RemoteCache<?, ?> remoteCache = getRemoteCache(session);
|
||||||
|
|
||||||
log.debugf("Will do bulk load of sessions from remote cache '%s' . First: %d, max: %d", cache.getName(), first, max);
|
log.debugf("Will do bulk load of sessions from remote cache '%s' . First: %d, max: %d", cache.getName(), first, max);
|
||||||
|
|
||||||
|
@ -167,4 +167,10 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
|
||||||
public void afterAllSessionsLoaded(BaseCacheInitializer initializer) {
|
public void afterAllSessionsLoaded(BaseCacheInitializer initializer) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get remoteCache, which may be secured
|
||||||
|
private RemoteCache getRemoteCache(KeycloakSession session) {
|
||||||
|
InfinispanConnectionProvider ispn = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
|
return ispn.getRemoteCache(cacheName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ import java.util.List;
|
||||||
@NamedQuery(name="findResourceIdByOwner", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :owner"),
|
@NamedQuery(name="findResourceIdByOwner", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :owner"),
|
||||||
@NamedQuery(name="findAnyResourceIdByOwner", query="select r.id from ResourceEntity r where r.owner = :owner"),
|
@NamedQuery(name="findAnyResourceIdByOwner", query="select r.id from ResourceEntity r where r.owner = :owner"),
|
||||||
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.uri = :uri"),
|
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.uri = :uri"),
|
||||||
@NamedQuery(name="findResourceIdByName", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.name = :name"),
|
@NamedQuery(name="findResourceIdByName", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :ownerId and r.name = :name"),
|
||||||
@NamedQuery(name="findResourceIdByType", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.type = :type"),
|
@NamedQuery(name="findResourceIdByType", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.type = :type"),
|
||||||
@NamedQuery(name="findResourceIdByServerId", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId "),
|
@NamedQuery(name="findResourceIdByServerId", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId "),
|
||||||
@NamedQuery(name="findResourceIdByScope", query="select r.id from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"),
|
@NamedQuery(name="findResourceIdByScope", query="select r.id from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"),
|
||||||
|
|
|
@ -237,11 +237,17 @@ public class JPAResourceStore implements ResourceStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Resource findByName(String name, String resourceServerId) {
|
public Resource findByName(String name, String resourceServerId) {
|
||||||
|
return findByName(name, resourceServerId, resourceServerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource findByName(String name, String ownerId, String resourceServerId) {
|
||||||
TypedQuery<String> query = entityManager.createNamedQuery("findResourceIdByName", String.class);
|
TypedQuery<String> query = entityManager.createNamedQuery("findResourceIdByName", String.class);
|
||||||
|
|
||||||
query.setFlushMode(FlushModeType.COMMIT);
|
query.setFlushMode(FlushModeType.COMMIT);
|
||||||
query.setParameter("serverId", resourceServerId);
|
query.setParameter("serverId", resourceServerId);
|
||||||
query.setParameter("name", name);
|
query.setParameter("name", name);
|
||||||
|
query.setParameter("ownerId", ownerId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String id = query.getSingleResult();
|
String id = query.getSingleResult();
|
||||||
|
|
|
@ -285,7 +285,7 @@ public final class AuthorizationProvider implements Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
throw new RuntimeException("Resource [" + id + "] does not exist");
|
throw new RuntimeException("Resource [" + id + "] does not exist or is not owned by the resource server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource.getId();
|
return resource.getId();
|
||||||
|
@ -459,6 +459,11 @@ public final class AuthorizationProvider implements Provider {
|
||||||
return delegate.findByName(name, resourceServerId);
|
return delegate.findByName(name, resourceServerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource findByName(String name, String ownerId, String resourceServerId) {
|
||||||
|
return delegate.findByName(name, ownerId, resourceServerId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Resource> findByType(String type, String resourceServerId) {
|
public List<Resource> findByType(String type, String resourceServerId) {
|
||||||
return delegate.findByType(type, resourceServerId);
|
return delegate.findByType(type, resourceServerId);
|
||||||
|
|
|
@ -18,11 +18,26 @@
|
||||||
|
|
||||||
package org.keycloak.authorization.policy.evaluation;
|
package org.keycloak.authorization.policy.evaluation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
import org.keycloak.authorization.Decision;
|
import org.keycloak.authorization.Decision;
|
||||||
import org.keycloak.authorization.Decision.Effect;
|
import org.keycloak.authorization.Decision.Effect;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.permission.ResourcePermission;
|
import org.keycloak.authorization.permission.ResourcePermission;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
import org.keycloak.models.utils.RoleUtils;
|
||||||
import org.keycloak.representations.idm.authorization.Logic;
|
import org.keycloak.representations.idm.authorization.Logic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,6 +51,7 @@ public class DefaultEvaluation implements Evaluation {
|
||||||
private final Policy policy;
|
private final Policy policy;
|
||||||
private final Policy parentPolicy;
|
private final Policy parentPolicy;
|
||||||
private final AuthorizationProvider authorizationProvider;
|
private final AuthorizationProvider authorizationProvider;
|
||||||
|
private final Realm realm;
|
||||||
private Effect effect;
|
private Effect effect;
|
||||||
|
|
||||||
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider) {
|
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider) {
|
||||||
|
@ -45,6 +61,7 @@ public class DefaultEvaluation implements Evaluation {
|
||||||
this.policy = policy;
|
this.policy = policy;
|
||||||
this.decision = decision;
|
this.decision = decision;
|
||||||
this.authorizationProvider = authorizationProvider;
|
this.authorizationProvider = authorizationProvider;
|
||||||
|
this.realm = createRealm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -84,6 +101,11 @@ public class DefaultEvaluation implements Evaluation {
|
||||||
return this.policy;
|
return this.policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Realm getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthorizationProvider getAuthorizationProvider() {
|
public AuthorizationProvider getAuthorizationProvider() {
|
||||||
return authorizationProvider;
|
return authorizationProvider;
|
||||||
|
@ -102,4 +124,128 @@ public class DefaultEvaluation implements Evaluation {
|
||||||
deny();
|
deny();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Realm createRealm() {
|
||||||
|
return new Realm() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserInGroup(String id, String groupId, boolean checkParent) {
|
||||||
|
KeycloakSession session = authorizationProvider.getKeycloakSession();
|
||||||
|
UserModel user = getUser(id, session);
|
||||||
|
|
||||||
|
if (Objects.isNull(user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
GroupModel group = KeycloakModelUtils.findGroupByPath(realm, groupId);
|
||||||
|
|
||||||
|
if (Objects.isNull(group)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkParent) {
|
||||||
|
return RoleUtils.isMember(user.getGroups(), group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.isMemberOf(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserModel getUser(String id, KeycloakSession session) {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
UserModel user = session.users().getUserById(id, realm);
|
||||||
|
|
||||||
|
if (Objects.isNull(user)) {
|
||||||
|
user = session.users().getUserByUsername(id, realm);
|
||||||
|
|
||||||
|
if (Objects.isNull(user)) {
|
||||||
|
user = session.users().getUserByEmail(id, realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserInRealmRole(String id, String roleName) {
|
||||||
|
KeycloakSession session = authorizationProvider.getKeycloakSession();
|
||||||
|
UserModel user = getUser(id, session);
|
||||||
|
|
||||||
|
if (Objects.isNull(user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<RoleModel> roleMappings = user.getRoleMappings().stream()
|
||||||
|
.filter(role -> !role.isClientRole())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
return RoleUtils.hasRole(roleMappings, session.getContext().getRealm().getRole(roleName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserInClientRole(String id, String clientId, String roleName) {
|
||||||
|
KeycloakSession session = authorizationProvider.getKeycloakSession();
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
UserModel user = getUser(id, session);
|
||||||
|
|
||||||
|
if (Objects.isNull(user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<RoleModel> roleMappings = user.getRoleMappings().stream()
|
||||||
|
.filter(role -> role.isClientRole() && ClientModel.class.cast(role.getContainer()).getClientId().equals(clientId))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
if (roleMappings.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RoleModel role = realm.getClientById(ClientModel.class.cast(roleMappings.iterator().next().getContainer()).getId()).getRole(roleName);
|
||||||
|
|
||||||
|
if (Objects.isNull(role)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RoleUtils.hasRole(roleMappings, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGroupInRole(String id, String role) {
|
||||||
|
KeycloakSession session = authorizationProvider.getKeycloakSession();
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
GroupModel group = KeycloakModelUtils.findGroupByPath(realm, id);
|
||||||
|
|
||||||
|
return RoleUtils.hasRoleFromGroup(group, realm.getRole(role), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getUserRealmRoles(String id) {
|
||||||
|
return getUser(id, authorizationProvider.getKeycloakSession()).getRoleMappings().stream()
|
||||||
|
.filter(role -> !role.isClientRole())
|
||||||
|
.map(RoleModel::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getUserClientRoles(String id, String clientId) {
|
||||||
|
return getUser(id, authorizationProvider.getKeycloakSession()).getRoleMappings().stream()
|
||||||
|
.filter(role -> role.isClientRole())
|
||||||
|
.map(RoleModel::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getUserGroups(String id) {
|
||||||
|
return getUser(id, authorizationProvider.getKeycloakSession()).getGroups().stream()
|
||||||
|
.map(ModelToRepresentation::buildGroupPath)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getUserAttributes(String id) {
|
||||||
|
Map<String, List<String>> attributes = getUser(id, authorizationProvider.getKeycloakSession()).getAttributes();
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,13 @@ public interface Evaluation {
|
||||||
*/
|
*/
|
||||||
Policy getPolicy();
|
Policy getPolicy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Realm} that can be used by policies to query information.
|
||||||
|
*
|
||||||
|
* @return a {@link Realm} instance
|
||||||
|
*/
|
||||||
|
Realm getRealm();
|
||||||
|
|
||||||
AuthorizationProvider getAuthorizationProvider();
|
AuthorizationProvider getAuthorizationProvider();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -117,7 +117,7 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
|
||||||
Resource resource = resourceStore.findById(permission.getResourceId(), resourceServer.getId());
|
Resource resource = resourceStore.findById(permission.getResourceId(), resourceServer.getId());
|
||||||
|
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
resource = resourceStore.findByName(permission.getResourceId(), resourceServer.getId());
|
resource = resourceStore.findByName(permission.getResourceId(), identity.getId(), resourceServer.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resource.isOwnerManagedAccess() || resource.getOwner().equals(identity.getId()) || resource.getOwner().equals(resourceServer.getId())) {
|
if (!resource.isOwnerManagedAccess() || resource.getOwner().equals(identity.getId()) || resource.getOwner().equals(resourceServer.getId())) {
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.keycloak.authorization.policy.evaluation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface provides methods to query information from a realm.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public interface Realm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Checks whether or not a user with the given <code>id</code> is a member of the given <code>group</code>.
|
||||||
|
*
|
||||||
|
* <p>This method will also consider memberships where the user is a member of any child group of the given <code>group</code>.
|
||||||
|
* For instance, if user is member of <code>/Group A/Group B</code> and this method is checking if user is a member of <code>/Group A</code>
|
||||||
|
* the result will be <code>true</code> given that the user is a member of a child group of <code>/Group A</code>.
|
||||||
|
*
|
||||||
|
* @param id the user id. It can be the id, username or email
|
||||||
|
* @param group the group path. For instance, /Group A/Group B.
|
||||||
|
* @return true if user is a member of the given group. Otherwise returns false.
|
||||||
|
*/
|
||||||
|
default boolean isUserInGroup(String id, String group) {
|
||||||
|
return isUserInGroup(id, group, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether or not a user with the given <code>id</code> is a member of the given <code>group</code>.
|
||||||
|
*
|
||||||
|
* @param id the user id. It can be the id, username or email
|
||||||
|
* @param group the group path. For instance, /Group A/Group B.
|
||||||
|
* @param checkParent if true, this method returns true even though the user is not directly associated with the given group but a member of any child of the group.
|
||||||
|
* @return true if user is a member of the given group. Otherwise returns false.
|
||||||
|
*/
|
||||||
|
boolean isUserInGroup(String id, String group, boolean checkParent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether or not a user with the given <code>id</code> is granted with the given realm <code>role</code>.
|
||||||
|
*
|
||||||
|
* @param id the user id. It can be the id, username or email
|
||||||
|
* @param role the role name
|
||||||
|
* @return true if the user is granted with the role. Otherwise, false.
|
||||||
|
*/
|
||||||
|
boolean isUserInRealmRole(String id, String role);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether or not a user with the given <code>id</code> is granted with the given client <code>role</code>.
|
||||||
|
*
|
||||||
|
* @param id the user id. It can be the id, username or email
|
||||||
|
* @param clientId the client id
|
||||||
|
* @param role the role name
|
||||||
|
* @return true if the user is granted with the role. Otherwise, false.
|
||||||
|
*/
|
||||||
|
boolean isUserInClientRole(String id, String clientId, String role);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether or not a <code>group</code> is granted with the given realm <code>role</code>.
|
||||||
|
*
|
||||||
|
* @param group the group path. For instance, /Group A/Group B.
|
||||||
|
* @param role the role name
|
||||||
|
* @return true if the group is granted with the role. Otherwise, false.
|
||||||
|
*/
|
||||||
|
boolean isGroupInRole(String group, String role);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all realm roles granted for a user with the given <code>id</code>.
|
||||||
|
*
|
||||||
|
* @param id the user id. It can be the id, username or email
|
||||||
|
* @return the roles granted to the user
|
||||||
|
*/
|
||||||
|
List<String> getUserRealmRoles(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all client roles granted for a user with the given <code>id</code>.
|
||||||
|
*
|
||||||
|
* @param id the user id. It can be the id, username or email
|
||||||
|
* @param clientId the client id
|
||||||
|
* @return the roles granted to the user
|
||||||
|
*/
|
||||||
|
List<String> getUserClientRoles(String id, String clientId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all groups which the user with the given <code>id</code> is a member.
|
||||||
|
*
|
||||||
|
* @param id the user id. It can be the id, username or email
|
||||||
|
* @return the groups which the user is a member
|
||||||
|
*/
|
||||||
|
List<String> getUserGroups(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all attributes associated with the a user with the given <code>id</code>.
|
||||||
|
*
|
||||||
|
* @param id the user id. It can be the id, username or email
|
||||||
|
* @return a map with the attributes associated with the user
|
||||||
|
*/
|
||||||
|
Map<String, List<String>> getUserAttributes(String id);
|
||||||
|
}
|
|
@ -97,7 +97,7 @@ public interface ResourceStore {
|
||||||
List<Resource> findByScope(List<String> id, String resourceServerId);
|
List<Resource> findByScope(List<String> id, String resourceServerId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a {@link Resource} by its name.
|
* Find a {@link Resource} by its name where the owner is the resource server itself.
|
||||||
*
|
*
|
||||||
* @param name the name of the resource
|
* @param name the name of the resource
|
||||||
* @param resourceServerId the identifier of the resource server
|
* @param resourceServerId the identifier of the resource server
|
||||||
|
@ -105,6 +105,16 @@ public interface ResourceStore {
|
||||||
*/
|
*/
|
||||||
Resource findByName(String name, String resourceServerId);
|
Resource findByName(String name, String resourceServerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a {@link Resource} by its name where the owner is the given <code>ownerId</code>.
|
||||||
|
*
|
||||||
|
* @param name the name of the resource
|
||||||
|
* @param ownerId the owner id
|
||||||
|
* @param resourceServerId the identifier of the resource server
|
||||||
|
* @return a resource with the given name
|
||||||
|
*/
|
||||||
|
Resource findByName(String name, String ownerId, String resourceServerId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds all {@link Resource} with the given type.
|
* Finds all {@link Resource} with the given type.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2275,7 +2275,7 @@ public class RepresentationToModel {
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
resource = storeFactory.getResourceStore().findByName(resourceId, policy.getResourceServer().getId());
|
resource = storeFactory.getResourceStore().findByName(resourceId, policy.getResourceServer().getId());
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
throw new RuntimeException("Resource with id or name [" + resourceId + "] does not exist");
|
throw new RuntimeException("Resource with id or name [" + resourceId + "] does not exist or is not owned by the resource server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2303,28 +2303,6 @@ public class RepresentationToModel {
|
||||||
|
|
||||||
public static Resource toModel(ResourceRepresentation resource, ResourceServer resourceServer, AuthorizationProvider authorization) {
|
public static Resource toModel(ResourceRepresentation resource, ResourceServer resourceServer, AuthorizationProvider authorization) {
|
||||||
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
|
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
|
||||||
Resource existing;
|
|
||||||
|
|
||||||
if (resource.getId() != null) {
|
|
||||||
existing = resourceStore.findById(resource.getId(), resourceServer.getId());
|
|
||||||
} else {
|
|
||||||
existing = resourceStore.findByName(resource.getName(), resourceServer.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existing != null) {
|
|
||||||
existing.setName(resource.getName());
|
|
||||||
existing.setDisplayName(resource.getDisplayName());
|
|
||||||
existing.setType(resource.getType());
|
|
||||||
existing.setUri(resource.getUri());
|
|
||||||
existing.setIconUri(resource.getIconUri());
|
|
||||||
existing.setOwnerManagedAccess(Boolean.TRUE.equals(resource.getOwnerManagedAccess()));
|
|
||||||
existing.updateScopes(resource.getScopes().stream()
|
|
||||||
.map((ScopeRepresentation scope) -> toModel(scope, resourceServer, authorization))
|
|
||||||
.collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceOwnerRepresentation owner = resource.getOwner();
|
ResourceOwnerRepresentation owner = resource.getOwner();
|
||||||
|
|
||||||
if (owner == null) {
|
if (owner == null) {
|
||||||
|
@ -2338,12 +2316,6 @@ public class RepresentationToModel {
|
||||||
throw new RuntimeException("No owner specified for resource [" + resource.getName() + "].");
|
throw new RuntimeException("No owner specified for resource [" + resource.getName() + "].");
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientModel clientModel = authorization.getRealm().getClientById(resourceServer.getId());
|
|
||||||
|
|
||||||
if (ownerId.equals(clientModel.getClientId())) {
|
|
||||||
ownerId = resourceServer.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resourceServer.getId().equals(ownerId)) {
|
if (!resourceServer.getId().equals(ownerId)) {
|
||||||
RealmModel realm = authorization.getRealm();
|
RealmModel realm = authorization.getRealm();
|
||||||
KeycloakSession keycloakSession = authorization.getKeycloakSession();
|
KeycloakSession keycloakSession = authorization.getKeycloakSession();
|
||||||
|
@ -2361,6 +2333,28 @@ public class RepresentationToModel {
|
||||||
ownerId = ownerModel.getId();
|
ownerId = ownerModel.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Resource existing;
|
||||||
|
|
||||||
|
if (resource.getId() != null) {
|
||||||
|
existing = resourceStore.findById(resource.getId(), resourceServer.getId());
|
||||||
|
} else {
|
||||||
|
existing = resourceStore.findByName(resource.getName(), ownerId, resourceServer.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing != null) {
|
||||||
|
existing.setName(resource.getName());
|
||||||
|
existing.setDisplayName(resource.getDisplayName());
|
||||||
|
existing.setType(resource.getType());
|
||||||
|
existing.setUri(resource.getUri());
|
||||||
|
existing.setIconUri(resource.getIconUri());
|
||||||
|
existing.setOwnerManagedAccess(Boolean.TRUE.equals(resource.getOwnerManagedAccess()));
|
||||||
|
existing.updateScopes(resource.getScopes().stream()
|
||||||
|
.map((ScopeRepresentation scope) -> toModel(scope, resourceServer, authorization))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
Resource model = resourceStore.create(resource.getName(), resourceServer, ownerId);
|
Resource model = resourceStore.create(resource.getName(), resourceServer, ownerId);
|
||||||
|
|
||||||
model.setDisplayName(resource.getDisplayName());
|
model.setDisplayName(resource.getDisplayName());
|
||||||
|
|
|
@ -205,27 +205,6 @@
|
||||||
<profile>
|
<profile>
|
||||||
<id>jboss-release</id>
|
<id>jboss-release</id>
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>false</enabled>
|
|
||||||
</snapshots>
|
|
||||||
<id>central</id>
|
|
||||||
<name>bintray</name>
|
|
||||||
<url>https://jcenter.bintray.com</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
<pluginRepositories>
|
|
||||||
<pluginRepository>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>false</enabled>
|
|
||||||
</snapshots>
|
|
||||||
<id>central</id>
|
|
||||||
<name>bintray</name>
|
|
||||||
<url>https://jcenter.bintray.com</url>
|
|
||||||
</pluginRepository>
|
|
||||||
</pluginRepositories>
|
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
@ -289,6 +268,28 @@
|
||||||
<artifactId>swagger2markup-maven-plugin</artifactId>
|
<artifactId>swagger2markup-maven-plugin</artifactId>
|
||||||
<version>1.1.0</version>
|
<version>1.1.0</version>
|
||||||
|
|
||||||
|
<!-- Replace the dependencies that aren't in Maven Central -->
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ca.szc.thirdparty.nl.jworks.markdown_to_asciidoc</groupId>
|
||||||
|
<artifactId>markdown_to_asciidoc</artifactId>
|
||||||
|
<!-- Keep in sync with markup-document-builder's dependency -->
|
||||||
|
<version>1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.swagger2markup</groupId>
|
||||||
|
<artifactId>swagger2markup</artifactId>
|
||||||
|
<!-- Keep in sync with swagger2markup-maven-plugin's dependency -->
|
||||||
|
<version>1.1.0</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>nl.jworks.markdown_to_asciidoc</groupId>
|
||||||
|
<artifactId>markdown_to_asciidoc</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>gen-asciidoc</id>
|
<id>gen-asciidoc</id>
|
||||||
|
|
|
@ -57,6 +57,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
public static final String MAPPING_SOURCE_SELECTION = "x509-cert-auth.mapping-source-selection";
|
public static final String MAPPING_SOURCE_SELECTION = "x509-cert-auth.mapping-source-selection";
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
|
||||||
|
public static final String MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL = "Subject's Alternative Name E-mail";
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_CN = "Subject's Common Name";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_CN = "Subject's Common Name";
|
||||||
public static final String MAPPING_SOURCE_CERT_ISSUERDN = "Match IssuerDN using regular expression";
|
public static final String MAPPING_SOURCE_CERT_ISSUERDN = "Match IssuerDN using regular expression";
|
||||||
public static final String MAPPING_SOURCE_CERT_ISSUERDN_EMAIL = "Issuer's e-mail";
|
public static final String MAPPING_SOURCE_CERT_ISSUERDN_EMAIL = "Issuer's e-mail";
|
||||||
|
@ -146,6 +147,9 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
.either(UserIdentityExtractor.getX500NameExtractor(BCStyle.EmailAddress, subject))
|
.either(UserIdentityExtractor.getX500NameExtractor(BCStyle.EmailAddress, subject))
|
||||||
.or(UserIdentityExtractor.getX500NameExtractor(BCStyle.E, subject));
|
.or(UserIdentityExtractor.getX500NameExtractor(BCStyle.E, subject));
|
||||||
break;
|
break;
|
||||||
|
case SUBJECTALTNAME_EMAIL:
|
||||||
|
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
|
||||||
|
break;
|
||||||
case ISSUERDN_CN:
|
case ISSUERDN_CN:
|
||||||
extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, issuer);
|
extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, issuer);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -43,6 +43,7 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_CN;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_CN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_EMAIL;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SERIALNUMBER;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SERIALNUMBER;
|
||||||
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
|
||||||
|
@ -68,6 +69,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
|
||||||
private static final String[] mappingSources = {
|
private static final String[] mappingSources = {
|
||||||
MAPPING_SOURCE_CERT_SUBJECTDN,
|
MAPPING_SOURCE_CERT_SUBJECTDN,
|
||||||
MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL,
|
MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL,
|
||||||
|
MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL,
|
||||||
MAPPING_SOURCE_CERT_SUBJECTDN_CN,
|
MAPPING_SOURCE_CERT_SUBJECTDN_CN,
|
||||||
MAPPING_SOURCE_CERT_ISSUERDN,
|
MAPPING_SOURCE_CERT_ISSUERDN,
|
||||||
MAPPING_SOURCE_CERT_ISSUERDN_EMAIL,
|
MAPPING_SOURCE_CERT_ISSUERDN_EMAIL,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue