KEYCLOAK-8047 Make Photoz tests great: run them on undertow + make them

stable
This commit is contained in:
mhajas 2018-10-02 10:40:35 +02:00 committed by Pavel Drozd
parent 29f8187978
commit 6d04247947
43 changed files with 919 additions and 1347 deletions

View file

@ -30,6 +30,7 @@ import java.lang.reflect.Field;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -51,6 +52,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment; import org.jboss.resteasy.spi.ResteasyDeployment;
import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.ClassAsset; import org.jboss.shrinkwrap.api.asset.ClassAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.descriptor.api.Descriptor; import org.jboss.shrinkwrap.descriptor.api.Descriptor;
@ -125,9 +127,20 @@ public class UndertowAppServer implements DeployableContainer<UndertowAppServerC
di = ((UndertowWebArchive) archive).getDeploymentInfo(); di = ((UndertowWebArchive) archive).getDeploymentInfo();
} else if (archive instanceof WebArchive) { } else if (archive instanceof WebArchive) {
WebArchive webArchive = (WebArchive)archive; WebArchive webArchive = (WebArchive)archive;
Optional<Node> applicationClassNode = archive.getContent(archivePath ->
archivePath.get().startsWith("/WEB-INF/classes/") && archivePath.get().endsWith("Application.class"))
.values().stream().findFirst();
if (isJaxrsApp(webArchive)) { if (isJaxrsApp(webArchive)) {
di = new UndertowDeployerHelper().getDeploymentInfo(configuration, webArchive, di = new UndertowDeployerHelper().getDeploymentInfo(configuration, webArchive,
undertow.undertowDeployment(getCustomResteasyDeployment(webArchive))); undertow.undertowDeployment(getCustomResteasyDeployment(webArchive)));
} else if (applicationClassNode.isPresent()) {
String applicationPath = applicationClassNode.get().getPath().get();
ResteasyDeployment deployment = new ResteasyDeployment();
deployment.setApplicationClass(extractClassName(applicationPath));
di = new UndertowDeployerHelper().getDeploymentInfo(configuration, (WebArchive) archive, undertow.undertowDeployment(deployment));
} else { } else {
di = new UndertowDeployerHelper().getDeploymentInfo(configuration, webArchive); di = new UndertowDeployerHelper().getDeploymentInfo(configuration, webArchive);
} }
@ -155,6 +168,14 @@ public class UndertowAppServer implements DeployableContainer<UndertowAppServerC
createHttpContextForDeploymentInfo(di)); createHttpContextForDeploymentInfo(di));
} }
private String extractClassName(String applicationPath) {
applicationPath = applicationPath
.substring(0, applicationPath.lastIndexOf(".class")) // Remove .class
.replaceFirst("^/WEB-INF/classes/", ""); // Remove /WEB-INF/classes/ from beginning
return applicationPath.replaceAll("/", ".");
}
@Override @Override
public void undeploy(Archive<?> archive) throws DeploymentException { public void undeploy(Archive<?> archive) throws DeploymentException {
log.info("Undeploying archive " + archive.getName()); log.info("Undeploying archive " + archive.getName());

View file

@ -43,6 +43,8 @@ public class UndertowDeploymentArchiveProcessor implements ApplicationArchivePro
modifySAMLAdapterConfig(archive, DeploymentArchiveProcessorUtils.SAML_ADAPTER_CONFIG_PATH); modifySAMLAdapterConfig(archive, DeploymentArchiveProcessorUtils.SAML_ADAPTER_CONFIG_PATH);
modifySAMLAdapterConfig(archive, DeploymentArchiveProcessorUtils.SAML_ADAPTER_CONFIG_PATH_TENANT1); modifySAMLAdapterConfig(archive, DeploymentArchiveProcessorUtils.SAML_ADAPTER_CONFIG_PATH_TENANT1);
modifySAMLAdapterConfig(archive, DeploymentArchiveProcessorUtils.SAML_ADAPTER_CONFIG_PATH_TENANT2); modifySAMLAdapterConfig(archive, DeploymentArchiveProcessorUtils.SAML_ADAPTER_CONFIG_PATH_TENANT2);
modifyOIDCAdapterConfig(archive, DeploymentArchiveProcessorUtils.ADAPTER_CONFIG_PATH_JS);
} }
private void modifyWebXML(Archive<?> archive, TestClass testClass) { private void modifyWebXML(Archive<?> archive, TestClass testClass) {

View file

@ -32,7 +32,7 @@
}, },
{ {
"name" : "Album Resource", "name" : "Album Resource",
"path" : "/album/{id}", "path" : "/album/{id}/",
"methods" : [ "methods" : [
{ {
"method": "DELETE", "method": "DELETE",

View file

@ -5,34 +5,63 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Photoz HTML5 Client</title> <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="lib/jwt-decode.min.js"></script>
<script src="http://localhost:8180/auth/js/keycloak.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="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> </head>
<body data-ng-controller="TokenCtrl"> <h2>Result</h2>
<!--<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 data-ng-click="requestEntitlement()">Request Entitlement</a> |<a href="" ng-click="Identity.logout()">Sign Out</a>-->
<a href data-ng-click="showRpt()">Show Requesting Party Token </a> |
<a href data-ng-click="showAccessToken()">Show Access Token </a> |
<a id="entitlements" href data-ng-click="requestEntitlements()">Request Entitlements</a> |
<a id="entitlement" href data-ng-click="requestEntitlement()">Request Entitlement</a> |
<a id="my-account" 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>
<div style="display: none;" id="bearer"></div>
<pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="output"></pre> <pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="output"></pre>
<h2>Events</h2>
<pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="events"></pre>
<script>
function showExpires() {
if (!keycloak.tokenParsed) {
output("Not authenticated");
return;
}
var o = 'Token Expires:\t\t' + new Date((keycloak.tokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Token Expires in:\t' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds\n';
if (keycloak.refreshTokenParsed) {
o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
}
output(o);
}
function showError() {
output("Error: " + getParameterByName("error") + "\n" + "Error description: " + getParameterByName("error_description"));
}
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&#]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function output(data) {
if (typeof data === 'object') {
data = JSON.stringify(data, null, ' ');
}
document.getElementById('output').innerHTML = data;
}
function event(event) {
var e = document.getElementById('events').innerHTML;
document.getElementById('events').innerHTML = new Date().toLocaleString() + "\t" + event + "\n" + e;
}
</script>
</body> </body>
</html> </html>

View file

@ -1,256 +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;
});
$scope.Identity = Identity;
$scope.deleteAlbum = function (album) {
new Album(album).$delete({id: album.id}, function () {
$route.reload();
});
}
$scope.requestPathWithAnyProtectedScope = function() {
$http.get(apiUrl + '/scope-any').success(function (data) {
});
}
$scope.requestPathWithAllProtectedScope = function() {
$http.get(apiUrl + '/scope-all').success(function (data) {
});
}
$scope.getAllResources = function () {
Album.getAll(function (albums) {
$scope.albums = albums;
});
}
});
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) {
document.getElementById("output").innerHTML = JSON.stringify(jwt_decode(rpt), null, ' ');
});
}
$scope.requestEntitlement = function () {
Identity.authorization.entitlement('photoz-restful-api', {
"permissions": [
{
"id" : "Album Resource"
}
]
}).then(function (rpt) {
document.getElementById("output").innerHTML = JSON.stringify(jwt_decode(rpt), null, ' ');
});
}
$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('/');
});
};
$scope.createManaged = function () {
$scope.album.userManaged = true;
var newAlbum = new Album($scope.album);
newAlbum.$save({}, function (data) {
$location.path('/');
});
};
$scope.createWithInvalidUser = function () {
var newAlbum = new Album($scope.album);
newAlbum.$save({user: 'invalidUser'}, function (data) {
document.getElementById("output").innerHTML = 'Request was successful'
},
function (response) {
document.getElementById("output").innerHTML = response.data;
});
};
});
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'}, {
getAll: {method: 'GET', params: {getAll: true}, 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;
document.getElementById("bearer").innerHTML = 'rpt: Bearer ' + Identity.authorization.rpt;
} else {
request.headers.Authorization = 'Bearer ' + Identity.authc.token;
document.getElementById("bearer").innerHTML = 'authc: 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);
}
};
});

View file

@ -1,65 +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.claims.sub = keycloak.idTokenParsed.sub;
this.authc = {};
this.authc.token = keycloak.token;
this.logout = function () {
keycloak.logout();
};
this.hasRole = function (name) {
if (keycloak && keycloak.hasRealmRole(name)) {
return true;
}
return false;
};
this.isAdmin = function () {
return this.hasRole("admin");
};
this.account = function () {
keycloak.accountManagement();
}
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 );

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -1,9 +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>
<button ng-click="createManaged()" id="save-managed-album">Save Managed</button>
<button ng-click="createWithInvalidUser()" id="save-album-invalid">Save with invalid user</button>
</form>

View file

@ -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> | <a href="#" id="requestPathWithAnyProtectedScope" ng-click="requestPathWithAnyProtectedScope()">Any Scope Access</a> | <a href="#" id="requestPathWithAllProtectedScope" ng-click="requestPathWithAllProtectedScope()">All Scope Access</a> | <a href id="get-all-resources" ng-click="getAllResources()">Get All Resources</a>
<br/>
<br/>
<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">
<thead>
<tr>
<th>Your Albums</th>
</tr>
</thead>
<tbody>
<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>
</tbody>
</table>
</div>

View file

@ -1,6 +0,0 @@
<h1>My Profile</h1>
<form>
<p>Name: {{profile.userName}}</p>
<p>Total of albums: {{profile.totalAlbums}}</p>
</form>

View file

@ -99,7 +99,7 @@
"decisionStrategy": "UNANIMOUS", "decisionStrategy": "UNANIMOUS",
"config": { "config": {
"applyPolicies": "[]", "applyPolicies": "[]",
"code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n $evaluation.grant();\n}" "code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1') || contextAttributes.containsValue('kc.client.network.ip_address', '0:0:0:0:0:0:0:1')) {\n $evaluation.grant();\n}"
} }
}, },
{ {

View file

@ -70,6 +70,14 @@
<skip>false</skip> <skip>false</skip>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View file

@ -0,0 +1,88 @@
package org.keycloak.example.photoz;
import org.keycloak.example.photoz.entity.Album;
import org.keycloak.example.photoz.entity.Photo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author mhajas
*/
public class CustomDatabase {
private static final CustomDatabase INSTANCE = new CustomDatabase();
private List<Album> albums;
private List<Photo> photos;
private Long lastIndex = 0L;
public static final CustomDatabase create() {
return INSTANCE;
}
private CustomDatabase() {
albums = new ArrayList<>();
}
public List<Album> getAll() {
return albums;
}
public void addAlbum(Album a) {
a.setId(lastIndex++);
albums.add(a);
}
public void remove(Album albumToRemove) {
Iterator<Album> iter = albums.iterator();
while (iter.hasNext()) {
Album a = iter.next();
if (a.getId().equals(albumToRemove.getId())) {
iter.remove();
}
}
}
public Album findById(Long id) {
for (Album a : albums) {
if(a.getId().equals(id)) {
return a;
}
}
return null;
}
public Album findByName(String name) {
for (Album a : albums) {
if(a.getName().equals(name)) {
return a;
}
}
return null;
}
public List<Album> findByUserId(String userId) {
List<Album> result = new ArrayList<>();
for (Album a : albums) {
if (a.getUserId().equals(userId)) {
result.add(a);
}
}
return result;
}
public int cleanAll() {
int result = albums.size() + photos.size();
albums.clear();
photos.clear();
return result;
}
}

View file

@ -1,12 +1,27 @@
package org.keycloak.example.photoz; package org.keycloak.example.photoz;
import org.keycloak.example.photoz.admin.AdminAlbumService;
import org.keycloak.example.photoz.album.AlbumService;
import org.keycloak.example.photoz.album.ProfileService;
import org.keycloak.example.photoz.unsecured.UnsecuredService;
import javax.ws.rs.ApplicationPath; import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application; import javax.ws.rs.core.Application;
import java.util.LinkedHashSet;
import java.util.Set;
/** /**
* Basic auth app. * Basic auth app.
*/ */
@ApplicationPath("/") @ApplicationPath("/")
public class PhotozApplication extends Application { public class PhotozApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new LinkedHashSet<Class<?>>();
resources.add(AlbumService.class);
resources.add(AdminAlbumService.class);
resources.add(ProfileService.class);
resources.add(UnsecuredService.class);
return resources;
}
} }

View file

@ -17,10 +17,9 @@
*/ */
package org.keycloak.example.photoz.admin; package org.keycloak.example.photoz.admin;
import org.keycloak.example.photoz.CustomDatabase;
import org.keycloak.example.photoz.entity.Album; import org.keycloak.example.photoz.entity.Album;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
@ -39,8 +38,7 @@ public class AdminAlbumService {
public static final String SCOPE_ADMIN_ALBUM_MANAGE = "admin:manage"; public static final String SCOPE_ADMIN_ALBUM_MANAGE = "admin:manage";
@Inject private CustomDatabase entityManager = CustomDatabase.create();
private EntityManager entityManager;
@Context @Context
private HttpHeaders headers; private HttpHeaders headers;
@ -49,7 +47,7 @@ public class AdminAlbumService {
@Produces("application/json") @Produces("application/json")
public Response findAll() { public Response findAll() {
HashMap<String, List<Album>> albums = new HashMap<>(); HashMap<String, List<Album>> albums = new HashMap<>();
List<Album> result = this.entityManager.createQuery("from Album").getResultList(); List<Album> result = this.entityManager.getAll();
for (Album album : result) { for (Album album : result) {
//We need to compile this under JDK7 so we can't use lambdas //We need to compile this under JDK7 so we can't use lambdas

View file

@ -3,14 +3,12 @@ package org.keycloak.example.photoz.album;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.authorization.client.AuthzClient; import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext; import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.example.photoz.CustomDatabase;
import org.keycloak.example.photoz.entity.Album;
import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.authorization.client.resource.ProtectionResource; import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.example.photoz.entity.Album;
import org.keycloak.example.photoz.util.Transaction;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
@ -29,7 +27,6 @@ import javax.ws.rs.core.HttpHeaders;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@Path("/album") @Path("/album")
@Transaction
public class AlbumService { public class AlbumService {
private final Logger log = Logger.getLogger(AlbumService.class); private final Logger log = Logger.getLogger(AlbumService.class);
@ -37,8 +34,7 @@ public class AlbumService {
public static final String SCOPE_ALBUM_VIEW = "album:view"; public static final String SCOPE_ALBUM_VIEW = "album:view";
public static final String SCOPE_ALBUM_DELETE = "album:delete"; public static final String SCOPE_ALBUM_DELETE = "album:delete";
@Inject private CustomDatabase customDatabase = CustomDatabase.create();
private EntityManager entityManager;
@Context @Context
private HttpServletRequest request; private HttpServletRequest request;
@ -57,28 +53,28 @@ public class AlbumService {
newAlbum.setUserId(userId); newAlbum.setUserId(userId);
log.debug("PERSISTING " + newAlbum); log.debug("PERSISTING " + newAlbum);
entityManager.persist(newAlbum); customDatabase.addAlbum(newAlbum);
try { try {
createProtectedResource(newAlbum); createProtectedResource(newAlbum);
} catch (RuntimeException e) { } catch (RuntimeException e) {
log.debug("ERROR " + e); log.debug("ERROR " + e);
entityManager.remove(newAlbum); customDatabase.remove(newAlbum);
throw e; return Response.status(500).entity(e.getMessage()).build(); //
} }
return Response.ok(newAlbum).build(); return Response.ok(newAlbum).build();
} }
@Path("{id}") @Path("{name}")
@DELETE @DELETE
public Response delete(@PathParam("id") String id, @Context HttpHeaders headers) { public Response delete(@PathParam("name") String name, @Context HttpHeaders headers) {
printAuthHeaders(headers); printAuthHeaders(headers);
Album album = this.entityManager.find(Album.class, Long.valueOf(id)); Album album = this.customDatabase.findByName(name);
try { try {
deleteProtectedResource(album); deleteProtectedResource(album);
this.entityManager.remove(album); this.customDatabase.remove(album);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Could not delete album.", e); throw new RuntimeException("Could not delete album.", e);
} }
@ -90,23 +86,23 @@ public class AlbumService {
@Produces("application/json") @Produces("application/json")
public Response findAll(@QueryParam("getAll") Boolean getAll) { public Response findAll(@QueryParam("getAll") Boolean getAll) {
if (getAll != null && getAll) { if (getAll != null && getAll) {
return Response.ok(this.entityManager.createQuery("from Album").getResultList()).build(); return Response.ok(this.customDatabase.getAll()).build();
} else { } else {
return Response.ok(this.entityManager.createQuery("from Album where userId = '" + request.getUserPrincipal().getName() + "'").getResultList()).build(); return Response.ok(this.customDatabase.findByUserId(request.getUserPrincipal().getName())).build();
} }
} }
@GET @GET
@Path("{id}") @Path("{name}")
@Produces("application/json") @Produces("application/json")
public Response findById(@PathParam("id") String id) { public Response findById(@PathParam("name") String name) {
List result = this.entityManager.createQuery("from Album where id = " + Long.valueOf(id)).getResultList(); Album result = this.customDatabase.findByName(name);
if (result.isEmpty()) { if (result == null) {
return Response.status(Status.NOT_FOUND).build(); return Response.status(Status.NOT_FOUND).build();
} }
return Response.ok(result.get(0)).build(); return Response.ok(result).build();
} }
private void createProtectedResource(Album album) { private void createProtectedResource(Album album) {
@ -117,7 +113,7 @@ public class AlbumService {
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW)); scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE)); scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album"); ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getName(), "http://photoz.com/album");
albumResource.setOwner(album.getUserId()); albumResource.setOwner(album.getUserId());
@ -132,7 +128,7 @@ public class AlbumService {
} }
private void deleteProtectedResource(Album album) { private void deleteProtectedResource(Album album) {
String uri = "/album/" + album.getId(); String uri = "/album/" + album.getName();
try { try {
ProtectionResource protection = getAuthzClient().protection(); ProtectionResource protection = getAuthzClient().protection();

View file

@ -17,8 +17,8 @@
*/ */
package org.keycloak.example.photoz.album; package org.keycloak.example.photoz.album;
import javax.inject.Inject; import org.keycloak.example.photoz.CustomDatabase;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
@ -36,14 +36,13 @@ public class ProfileService {
private static final String PROFILE_VIEW = "profile:view"; private static final String PROFILE_VIEW = "profile:view";
@Inject private CustomDatabase customDatabase = CustomDatabase.create();
private EntityManager entityManager;
@GET @GET
@Produces("application/json") @Produces("application/json")
public Response view(@Context HttpServletRequest request) { public Response view(@Context HttpServletRequest request) {
Principal userPrincipal = request.getUserPrincipal(); Principal userPrincipal = request.getUserPrincipal();
List albums = this.entityManager.createQuery("from Album where userId = '" + userPrincipal.getName() + "'").getResultList(); List albums = this.customDatabase.findByUserId(userPrincipal.getName());
return Response.ok(new Profile(userPrincipal.getName(), albums.size())).build(); return Response.ok(new Profile(userPrincipal.getName(), albums.size())).build();
} }

View file

@ -17,13 +17,12 @@
package org.keycloak.example.photoz.unsecured; package org.keycloak.example.photoz.unsecured;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.example.photoz.CustomDatabase;
/** /**
* Service used to ensure there is clean DB before test * Service used to ensure there is clean DB before test
@ -35,17 +34,15 @@ public class UnsecuredService {
private final Logger log = Logger.getLogger(UnsecuredService.class); private final Logger log = Logger.getLogger(UnsecuredService.class);
@Inject private CustomDatabase customDatabase = CustomDatabase.create();
private EntityManager entityManager;
@GET @GET
@Produces("application/json") @Produces("application/json")
public Response cleanAll() { public Response cleanAll() {
int deletedAlbums = entityManager.createQuery("delete from Album").executeUpdate(); int deleted = customDatabase.cleanAll();
int deletedPhotos = entityManager.createQuery("delete from Photo").executeUpdate();
if (deletedAlbums != 0 || deletedPhotos != 0) { if (deleted != 0) {
log.warnf("Database was not empty. Deleted {0} Albums, {1} Photos", deletedAlbums, deletedPhotos); log.warnf("Database was not empty. Deleted Albums + Photos {0}", deleted);
} else { } else {
log.debug("Database was clean before test"); log.debug("Database was clean before test");
} }

View file

@ -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();
}
}

View file

@ -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 {
}

View file

@ -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) throws Exception {
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();
}
}
}

View file

@ -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.jpa.HibernatePersistenceProvider</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:mem:test-keycloak-photoz-example" />
<property name="hibernate.connection.user" value="sa" />
<property name="hibernate.flushMode" value="COMMIT" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="false" />
</properties>
</persistence-unit>
</persistence>

View file

@ -3,7 +3,4 @@
xsi:schemaLocation=" xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<interceptors>
<class>org.keycloak.example.photoz.util.TransactionInterceptor</class>
</interceptors>
</beans> </beans>

View file

@ -31,7 +31,7 @@
}, },
{ {
"name" : "Album Resource", "name" : "Album Resource",
"path" : "/album/{id}", "path" : "/album/{id}/",
"methods" : [ "methods" : [
{ {
"method": "DELETE", "method": "DELETE",

View file

@ -85,6 +85,12 @@
<artifactId>hamcrest-all</artifactId> <artifactId>hamcrest-all</artifactId>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.11.1</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.subethamail</groupId> <groupId>org.subethamail</groupId>
<artifactId>subethasmtp</artifactId> <artifactId>subethasmtp</artifactId>
@ -139,12 +145,8 @@
<dependency> <dependency>
<groupId>org.keycloak.testsuite</groupId> <groupId>org.keycloak.testsuite</groupId>
<artifactId>photoz-restful-api</artifactId> <artifactId>photoz-restful-api</artifactId>
<type>war</type>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> <classifier>classes</classifier>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View file

@ -22,10 +22,13 @@ import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.test.api.ArquillianResource;
import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
import org.keycloak.testsuite.pages.ConsentPage;
import org.keycloak.testsuite.util.JavascriptBrowser; import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.util.UIUtils; import org.keycloak.testsuite.util.UIUtils;
import org.keycloak.testsuite.util.URLUtils; import org.keycloak.testsuite.util.URLUtils;
import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
import org.keycloak.testsuite.util.javascript.JavascriptTestExecutorWithAuthorization;
import org.keycloak.testsuite.util.javascript.ResponseValidator;
import org.keycloak.testsuite.util.javascript.XMLHttpRequest;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
@ -33,8 +36,6 @@ import org.openqa.selenium.support.FindBy;
import java.net.URL; import java.net.URL;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.util.UIUtils.clickLink; import static org.keycloak.testsuite.util.UIUtils.clickLink;
import static org.keycloak.testsuite.util.WaitUtils.pause; import static org.keycloak.testsuite.util.WaitUtils.pause;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad; import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
@ -61,9 +62,6 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
@JavascriptBrowser @JavascriptBrowser
protected OIDCLogin loginPage; protected OIDCLogin loginPage;
@Page
@JavascriptBrowser
protected ConsentPage consentPage;
@FindBy(xpath = "//a[@ng-click = 'Identity.logout()']") @FindBy(xpath = "//a[@ng-click = 'Identity.logout()']")
@JavascriptBrowser @JavascriptBrowser
@ -85,74 +83,59 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
@JavascriptBrowser @JavascriptBrowser
private WebElement output; private WebElement output;
private JavascriptTestExecutorWithAuthorization testExecutor;
private String apiUrl;
public void setTestExecutorPlayground(JavascriptTestExecutorWithAuthorization executor, String apiUrl) {
testExecutor = executor;
this.apiUrl = apiUrl;
}
public void createAlbum(String name) { public void createAlbum(String name) {
createAlbum(name, false); createAlbum(name, false);
} }
public void createAlbum(String name, boolean managed) { public void createAlbum(String name, boolean managed) {
if (managed) { createAlbum(name, managed, false, null);
createAlbum(name, "save-managed-album");
} else {
createAlbum(name, "save-album");
}
} }
public void createAlbum(String name, String buttonId) { public void createAlbum(String name, boolean managed, boolean invalidUser, ResponseValidator validator) {
log.debugf("Creating album {0} with buttonId: {1}", name, buttonId); testExecutor.sendXMLHttpRequest(
navigateTo(); XMLHttpRequest.create()
WebElement createAlbum = driver.findElement(By.id("create-album")); .method("POST")
waitUntilElement(createAlbum).is().clickable(); .url(apiUrl + "/album" + (invalidUser ? "?user=invalidUser" : ""))
createAlbum.click(); .content("JSON.stringify(JSON.parse('{\"name\" : \"" + name + "\", \"userManaged\": " + Boolean.toString(managed) + " }'))")
WebElement albumNameInput = driver.findElement(By.id("album.name")); .addHeader("Content-Type", "application/json; charset=UTF-8")
waitUntilElement(albumNameInput).is().present(); , validator);
UIUtils.setTextInputValue(albumNameInput, name);
waitUntilElement(albumNameInput).attribute(UIUtils.VALUE_ATTR_NAME).contains(name);
WebElement button = driver.findElement(By.id(buttonId));
waitUntilElement(button).is().clickable();
button.click();
pause(WAIT_AFTER_OPERATION);
if (buttonId.equals("save-album-invalid")) {
waitForPageToLoad();
assertThat(driver.getPageSource(), containsString("Could not register protected resource."));
} else {
waitUntilElement(albumNameInput).is().not().present();
}
} }
public void createAlbumWithInvalidUser(String name) {
createAlbum(name, "save-album-invalid"); public void createAlbumWithInvalidUser(String name, ResponseValidator validator) {
createAlbum(name, false, true, validator);
} }
@Override @Override
public URL getInjectedUrl() { public URL getInjectedUrl() {
return this.url; return this.url;
} }
public void deleteAlbum(String name, boolean shouldBeDenied) { public void deleteAlbum(String name, ResponseValidator validator) {
log.debugf("Deleting album {0}", name); testExecutor.sendXMLHttpRequest(
WebElement delete = driver.findElement(By.id("delete-" + name)); XMLHttpRequest.create()
waitUntilElement(delete).is().clickable(); .method("DELETE")
delete.click(); .url(apiUrl + "/album/" + name + "/") // it doesn't work without ending "/"
pause(WAIT_AFTER_OPERATION); , validator);
if (shouldBeDenied) {
waitForDenial();
} else {
waitUntilElement(delete).is().not().present();
}
} }
public void navigateToAdminAlbum(boolean shouldBeDenied) { public void navigateToAdminAlbum(ResponseValidator validator) {
log.debug("Navigating to Admin Album"); testExecutor.sendXMLHttpRequest(
URLUtils.navigateToUri(toString() + "/#/admin/album"); XMLHttpRequest.create()
.method("GET")
driver.navigate().refresh(); // This is sometimes necessary for loading the new policy settings .addHeader("Accept", "application/json")
waitForPageToLoad(); .url(apiUrl + "/admin/album")
pause(WAIT_AFTER_OPERATION); , validator);
if (shouldBeDenied) {
waitForDenial();
} else {
waitUntilElement(output).text().equalTo("");
}
} }
public void logOut() { public void logOut() {
@ -161,73 +144,24 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
clickLink(signOutButton); clickLink(signOutButton);
} }
public void requestEntitlement() { public void requestEntitlement(JavascriptStateValidator validator) {
waitUntilElement(entitlement).is().clickable(); testExecutor.executeAsyncScript("var callback = arguments[arguments.length - 1];" +
entitlement.click(); "window.authorization.entitlement('photoz-restful-api', {" +
waitForPageToLoad(); " \"permissions\": [" +
pause(WAIT_AFTER_OPERATION); " {" +
pause(WAIT_AFTER_OPERATION); " \"id\" : \"Album Resource\"" +
" }" +
" ]" +
"}).then(function (rpt) {" +
" callback(JSON.stringify(jwt_decode(rpt), null, ' '));" +
"});", validator);
} }
public void requestEntitlements() { public void requestEntitlements(JavascriptStateValidator validator) {
waitUntilElement(entitlements).is().clickable(); testExecutor.executeAsyncScript("var callback = arguments[arguments.length - 1];" +
entitlements.click(); "window.authorization.entitlement('photoz-restful-api').then(function (rpt) {" +
waitForPageToLoad(); " callback(JSON.stringify(jwt_decode(rpt), null, ' '));" +
pause(WAIT_AFTER_OPERATION); "});", validator);
pause(WAIT_AFTER_OPERATION);
}
public void login(String username, String password, String... scopes) throws InterruptedException {
String currentUrl = this.driver.getCurrentUrl();
if (scopes.length > 0) {
StringBuilder scopesValue = new StringBuilder();
for (String scope : scopes) {
if (scopesValue.length() != 0) {
scopesValue.append(" ");
}
scopesValue.append(scope);
}
scopesValue.append(" openid");
StringBuilder urlWithScopeParam = new StringBuilder(currentUrl);
int scopeIndex = currentUrl.indexOf("scope");
if (scopeIndex != -1) {
// Remove scope param from url
urlWithScopeParam.delete(scopeIndex, currentUrl.indexOf('&', scopeIndex));
// Add scope param to the end of query
urlWithScopeParam.append("&").append("scope=");
}
if (!currentUrl.contains("?")) {
urlWithScopeParam.append("?scope=");
}
urlWithScopeParam.append(scopesValue);
URLUtils.navigateToUri(urlWithScopeParam.toString());
}
this.loginPage.form().login(username, password);
waitForPageToLoad();//guess
try {
if (!isCurrent()) {
// simple check if we are at the consent page, if so just click 'Yes'
if (this.consentPage.isCurrent(driver)) {
consentPage.confirm();
}
}
} catch (Exception ignore) {
// ignore errors when checking consent page, if an error tests will also fail
}
pause(WAIT_AFTER_OPERATION);
} }
private void waitForDenial() { private void waitForDenial() {
@ -243,28 +177,18 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
pause(WAIT_AFTER_OPERATION); pause(WAIT_AFTER_OPERATION);
} }
public void viewAlbum(String name, boolean shouldBeDenied) { public void viewAlbum(String name, ResponseValidator validator) {
WebElement viewalbum = driver.findElement(By.xpath("//a[text() = '" + name + "']")); testExecutor.sendXMLHttpRequest(
waitUntilElement(viewalbum).is().clickable(); XMLHttpRequest.create()
viewalbum.click(); .method("GET")
waitForPageToLoad(); .addHeader("Accept", "application/json")
driver.navigate().refresh(); // This is sometimes necessary for loading the new policy settings .url(apiUrl + "/album/" + name + "/")
if (shouldBeDenied) { , validator);
waitForDenial();
} else {
waitForNotDenial();
}
waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountPage() { public void accountPage() {
navigateTo(); testExecutor.openAccountPage(null);
WebElement myAccount = driver.findElement(By.id("my-account"));
waitUntilElement(myAccount).is().clickable();
myAccount.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountMyResources() { public void accountMyResources() {
@ -273,7 +197,6 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
waitUntilElement(myResources).is().clickable(); waitUntilElement(myResources).is().clickable();
myResources.click(); myResources.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountMyResource(String name) { public void accountMyResource(String name) {
@ -282,7 +205,6 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
waitUntilElement(myResource).is().clickable(); waitUntilElement(myResource).is().clickable();
myResource.click(); myResource.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountGrantResource(String name, String requester) { public void accountGrantResource(String name, String requester) {
@ -291,7 +213,6 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
waitUntilElement(grantResource).is().clickable(); waitUntilElement(grantResource).is().clickable();
grantResource.click(); grantResource.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountGrantRemoveScope(String name, String requester, String scope) { public void accountGrantRemoveScope(String name, String requester, String scope) {
@ -300,7 +221,6 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
waitUntilElement(grantRemoveScope).is().clickable(); waitUntilElement(grantRemoveScope).is().clickable();
grantRemoveScope.click(); grantRemoveScope.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountRevokeResource(String name, String requester) { public void accountRevokeResource(String name, String requester) {
@ -309,7 +229,6 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
waitUntilElement(revokeResource).is().clickable(); waitUntilElement(revokeResource).is().clickable();
revokeResource.click(); revokeResource.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountShareResource(String name, String user) { public void accountShareResource(String name, String user) {
@ -323,7 +242,6 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
waitUntilElement(shareButton).is().clickable(); waitUntilElement(shareButton).is().clickable();
shareButton.click(); shareButton.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountShareRemoveScope(String name, String user, String scope) { public void accountShareRemoveScope(String name, String user, String scope) {
@ -344,7 +262,6 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
shareButton.click(); shareButton.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void accountDenyResource(String name) { public void accountDenyResource(String name) {
@ -353,25 +270,22 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
waitUntilElement(denyLink).is().clickable(); waitUntilElement(denyLink).is().clickable();
denyLink.click(); denyLink.click();
waitForPageToLoad(); waitForPageToLoad();
pause(WAIT_AFTER_OPERATION);
} }
public void requestResourceProtectedAnyScope(boolean shouldBeDenied) { public void requestResourceProtectedAnyScope(ResponseValidator validator) {
navigateTo(); testExecutor.sendXMLHttpRequest(
WebElement requestPathWithAnyProtectedScope = driver.findElement(By.id("requestPathWithAnyProtectedScope")); XMLHttpRequest.create()
waitUntilElement(requestPathWithAnyProtectedScope).is().clickable(); .method("GET")
requestPathWithAnyProtectedScope.click(); .url(apiUrl + "/scope-any")
if (shouldBeDenied) waitForDenial(); , validator);
pause(WAIT_AFTER_OPERATION);
} }
public void requestResourceProtectedAllScope(boolean shouldBeDenied) { public void requestResourceProtectedAllScope(ResponseValidator validator) {
navigateTo(); testExecutor.sendXMLHttpRequest(
WebElement requestPathWithAllProtectedScope = driver.findElement(By.id("requestPathWithAllProtectedScope")); XMLHttpRequest.create()
waitUntilElement(requestPathWithAllProtectedScope).is().clickable(); .method("GET")
requestPathWithAllProtectedScope.click(); .url(apiUrl + "/scope-all")
if (shouldBeDenied) waitForDenial(); , validator);
pause(WAIT_AFTER_OPERATION);
} }
public WebElement getOutput() { public WebElement getOutput() {
@ -380,8 +294,8 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
@Override @Override
public void navigateTo() { public void navigateTo() {
super.navigateTo(); driver.navigate().to(toString() + "/");
pause(WAIT_AFTER_OPERATION); waitForPageToLoad();
} }
@Override @Override

View file

@ -1,4 +1,4 @@
package org.keycloak.testsuite.adapter.javascript; package org.keycloak.testsuite.util.javascript;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;

View file

@ -1,4 +1,4 @@
package org.keycloak.testsuite.adapter.javascript; package org.keycloak.testsuite.util.javascript;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;

View file

@ -1,4 +1,4 @@
package org.keycloak.testsuite.adapter.javascript; package org.keycloak.testsuite.util.javascript;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.auth.page.login.OIDCLogin;
@ -9,23 +9,25 @@ import org.openqa.selenium.WebElement;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
/** /**
* @author mhajas * @author mhajas
*/ */
public class JavascriptTestExecutor { public class JavascriptTestExecutor {
private WebDriver jsDriver; protected WebDriver jsDriver;
private JavascriptExecutor jsExecutor; protected JavascriptExecutor jsExecutor;
private WebElement output; private WebElement output;
private WebElement events; protected WebElement events;
private OIDCLogin loginPage; private OIDCLogin loginPage;
private boolean configured; protected boolean configured;
public static JavascriptTestExecutor create(WebDriver driver, OIDCLogin loginPage) { public static JavascriptTestExecutor create(WebDriver driver, OIDCLogin loginPage) {
return new JavascriptTestExecutor(driver, loginPage); return new JavascriptTestExecutor(driver, loginPage);
} }
private JavascriptTestExecutor(WebDriver driver, OIDCLogin loginPage) { protected JavascriptTestExecutor(WebDriver driver, OIDCLogin loginPage) {
this.jsDriver = driver; this.jsDriver = driver;
driver.manage().timeouts().setScriptTimeout(10, TimeUnit.SECONDS); driver.manage().timeouts().setScriptTimeout(10, TimeUnit.SECONDS);
jsExecutor = (JavascriptExecutor) driver; jsExecutor = (JavascriptExecutor) driver;
@ -49,6 +51,7 @@ public class JavascriptTestExecutor {
else { else {
jsExecutor.executeScript("keycloak.login(" + options + ")"); jsExecutor.executeScript("keycloak.login(" + options + ")");
} }
waitForPageToLoad();
if (validator != null) { if (validator != null) {
validator.validate(jsDriver, output, events); validator.validate(jsDriver, output, events);
@ -65,6 +68,7 @@ public class JavascriptTestExecutor {
public JavascriptTestExecutor loginForm(UserRepresentation user, JavascriptStateValidator validator) { public JavascriptTestExecutor loginForm(UserRepresentation user, JavascriptStateValidator validator) {
loginPage.form().login(user); loginPage.form().login(user);
waitForPageToLoad();
if (validator != null) { if (validator != null) {
validator.validate(jsDriver, null, events); validator.validate(jsDriver, null, events);
@ -174,6 +178,20 @@ public class JavascriptTestExecutor {
return this; return this;
} }
public JavascriptTestExecutor openAccountPage(JavascriptStateValidator validator) {
jsExecutor.executeScript("window.keycloak.accountManagement()");
waitForPageToLoad();
// Leaving page -> loosing keycloak variable
configured = false;
if (validator != null) {
validator.validate(jsDriver, null, null);
}
return this;
}
public JavascriptTestExecutor getProfile() { public JavascriptTestExecutor getProfile() {
return getProfile(null); return getProfile(null);
} }
@ -235,6 +253,12 @@ public class JavascriptTestExecutor {
return this; return this;
} }
public boolean isLoggedIn() {
return (boolean) jsExecutor.executeScript("if (typeof keycloak !== 'undefined') {" +
"return keycloak.authenticated" +
"} else { return false}");
}
public JavascriptTestExecutor executeAsyncScript(String script) { public JavascriptTestExecutor executeAsyncScript(String script) {
return executeAsyncScript(script, null); return executeAsyncScript(script, null);
} }

View file

@ -0,0 +1,167 @@
package org.keycloak.testsuite.util.javascript;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.pages.ConsentPage;
import org.keycloak.testsuite.util.URLUtils;
import org.openqa.selenium.WebDriver;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
/**
* @author mhajas
*/
public class JavascriptTestExecutorWithAuthorization extends JavascriptTestExecutor {
public static JavascriptTestExecutorWithAuthorization create(WebDriver driver, OIDCLogin loginPage) {
return new JavascriptTestExecutorWithAuthorization(driver, loginPage);
}
private JavascriptTestExecutorWithAuthorization(WebDriver driver, OIDCLogin loginPage) {
super(driver, loginPage);
}
@Override
public JavascriptTestExecutorWithAuthorization init(JSObjectBuilder argumentsBuilder, JavascriptStateValidator validator) {
super.init(argumentsBuilder, validator);
Object output = jsExecutor.executeScript(
"var callback = arguments[arguments.length - 1];" +
"window.authorization = new KeycloakAuthorization(window.keycloak);" +
"while (typeof window.authorization === 'undefined') {}" + // Wait until authorization is initialized
"return 'Authz initialized'");
assertThat(output, is("Authz initialized"));
return this;
}
@Override
public JavascriptTestExecutorWithAuthorization login(JavascriptStateValidator validator) {
super.login(validator);
return this;
}
public JavascriptTestExecutorWithAuthorization loginFormWithScopesWithPossibleConsentPage(UserRepresentation user, JavascriptStateValidator validator, OAuthGrant oAuthGrantPage, String... scopes) {
String currentUrl = jsDriver.getCurrentUrl();
if (scopes.length > 0) {
StringBuilder scopesValue = new StringBuilder();
for (String scope : scopes) {
if (scopesValue.length() != 0) {
scopesValue.append(" ");
}
scopesValue.append(scope);
}
scopesValue.append(" openid");
StringBuilder urlWithScopeParam = new StringBuilder(currentUrl);
int scopeIndex = currentUrl.indexOf("scope");
if (scopeIndex != -1) {
// Remove scope param from url
urlWithScopeParam.delete(scopeIndex, currentUrl.indexOf('&', scopeIndex));
// Add scope param to the end of query
urlWithScopeParam.append("&").append("scope=");
}
if (!currentUrl.contains("?")) {
urlWithScopeParam.append("?scope=");
}
urlWithScopeParam.append(scopesValue);
URLUtils.navigateToUri(urlWithScopeParam.toString());
waitForPageToLoad();
}
loginFormWithPossibleConsentPage(user, oAuthGrantPage, validator);
return this;
}
public JavascriptTestExecutorWithAuthorization loginFormWithPossibleConsentPage(UserRepresentation user, OAuthGrant oAuthGrantPage, JavascriptStateValidator validator) {
super.loginForm(user);
try {
// simple check if we are at the consent page, if so just click 'Yes'
if (oAuthGrantPage.isCurrent(jsDriver)) {
oAuthGrantPage.accept();
waitForPageToLoad();
}
} catch (Exception ignore) {
// ignore errors when checking consent page, if an error tests will also fail
}
if (validator != null) {
validator.validate(jsDriver, null, events);
}
return this;
}
@Override
public JavascriptTestExecutor sendXMLHttpRequest(XMLHttpRequest request, ResponseValidator validator) {
// Intercept all requests and add rpt or token
// check if rpt is already present
Object o = jsExecutor.executeScript("if(window.authorization && window.authorization.rpt) return true; else return false;");
if (o == null || o.equals(false)) {
// RPT is not present yet, lets try to use bearer token
request.includeBearerToken();
} else {
// RPT token is present so we will use it
request.includeRpt();
}
// Try to send request
Map<String, Object> result = request.send(jsExecutor);
// If request was denied do UMA
if ((Long.valueOf(403).equals(result.get("status")) || Long.valueOf(401).equals(result.get("status")))) {
//extracting ticket from response
String headersString = (String) result.get("responseHeaders");
List<String> headersList = Arrays.asList(headersString.split("\r\n"));
String wwwAuthenticate = headersList.stream().filter(s -> s.toLowerCase().startsWith("www-authenticate:")).findFirst().get();
if (wwwAuthenticate.contains("UMA") && wwwAuthenticate.contains("ticket")) {
String ticket = Arrays.asList(wwwAuthenticate.split(",")).stream().filter(s -> s.startsWith("ticket")).findFirst().get();
ticket = ticket.substring(0, ticket.length() - 1).replaceFirst("ticket=\"", "");
// AuthorizationRequest for RPT
o = jsExecutor.executeAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"window.authorization" +
".authorize(" + JSObjectBuilder.create().add("ticket", ticket).build() + ")" +
".then(function (rpt) {callback(rpt)}, function() {callback('failed1')}, function() {callback('failed2')});");
o = jsExecutor.executeScript("if(window.authorization && window.authorization.rpt) return true; else return false;"); // return window.authorization && window.authorization.rpt doesn't work
if (o != null && o.equals(true)) {
request.includeRpt();
result = request.send(jsExecutor);
}
}
}
if (validator != null) {
validator.validate(result);
}
return this;
}
}

View file

@ -1,4 +1,4 @@
package org.keycloak.testsuite.adapter.javascript; package org.keycloak.testsuite.util.javascript;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;

View file

@ -1,4 +1,4 @@
package org.keycloak.testsuite.adapter.javascript; package org.keycloak.testsuite.util.javascript;
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.JavascriptExecutor;
@ -26,6 +26,10 @@ public class XMLHttpRequest {
return this; return this;
} }
public String getUrl() {
return url;
}
public XMLHttpRequest method(String method) { public XMLHttpRequest method(String method) {
this.method = method; this.method = method;
return this; return this;
@ -46,6 +50,16 @@ public class XMLHttpRequest {
return this; return this;
} }
public XMLHttpRequest includeBearerToken() {
addHeader("Authorization", "Bearer ' + keycloak.token + '");
return this;
}
public XMLHttpRequest includeRpt() {
addHeader("Authorization", "Bearer ' + authorization.rpt + '");
return this;
}
public Map<String, Object> send(JavascriptExecutor jsExecutor) { public Map<String, Object> send(JavascriptExecutor jsExecutor) {
String requestCode = "var callback = arguments[arguments.length - 1];" + String requestCode = "var callback = arguments[arguments.length - 1];" +
"var req = new XMLHttpRequest();" + "var req = new XMLHttpRequest();" +
@ -53,7 +67,7 @@ public class XMLHttpRequest {
getHeadersString() + getHeadersString() +
" req.onreadystatechange = function () {" + " req.onreadystatechange = function () {" +
" if (req.readyState == 4) {" + " if (req.readyState == 4) {" +
" callback({\"status\" : req.status, \"reponseText\" : req.reponseText, \"responseHeaders\" : req.getAllResponseHeaders().toLowerCase(), \"res\" : req.response})" + " callback({\"status\" : req.status, \"responseHeaders\" : req.getAllResponseHeaders(), \"res\" : req.response, \"req\" : req})" +
" }" + " }" +
" };" + " };" +
" req.send(" + content + ");"; " req.send(" + content + ");";

View file

@ -20,25 +20,19 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy; import org.apache.http.impl.client.LaxRedirectStrategy;
import org.hamcrest.CoreMatchers;
import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientScopesResource; import org.keycloak.admin.client.resource.ClientScopesResource;
import org.keycloak.admin.client.resource.ClientsResource; import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RealmsResource;
import org.keycloak.admin.client.resource.ResourcesResource; import org.keycloak.admin.client.resource.ResourcesResource;
import org.keycloak.admin.client.resource.RoleResource; import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
@ -54,84 +48,89 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp; import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.arquillian.AppServerTestEnricher;
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
import org.keycloak.testsuite.util.ContainerAssume; import org.keycloak.testsuite.util.ContainerAssume;
import org.keycloak.testsuite.util.DroneUtils; import org.keycloak.testsuite.util.DroneUtils;
import org.keycloak.testsuite.util.JavascriptBrowser; import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.utils.io.IOUtil; import org.keycloak.testsuite.util.javascript.JavascriptTestExecutorWithAuthorization;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.Matchers.empty; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.utils.io.IOUtil.loadJson; import static org.keycloak.testsuite.utils.io.IOUtil.loadJson;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm; import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad; import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.keycloak.testsuite.arquillian.AppServerTestEnricher;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAdapterTest { public abstract class AbstractPhotozExampleAdapterTest extends AbstractPhotozJavascriptExecutorTest {
private static final String REALM_NAME = "photoz";
protected static final String RESOURCE_SERVER_ID = "photoz-restful-api"; protected static final String RESOURCE_SERVER_ID = "photoz-restful-api";
protected static final String ALICE_ALBUM_NAME = "Alice-Family-Album";
private static final int TOKEN_LIFESPAN_LEEWAY = 3; // seconds private static final int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
@ArquillianResource @ArquillianResource
private Deployer deployer; private Deployer deployer;
// Javascript browser needed KEYCLOAK-4703
@Drone
@JavascriptBrowser
protected WebDriver jsDriver;
@Page
@JavascriptBrowser
protected OIDCLogin jsDriverTestRealmLoginPage;
@Page @Page
@JavascriptBrowser @JavascriptBrowser
private PhotozClientAuthzTestApp clientPage; private PhotozClientAuthzTestApp clientPage;
@Page
@JavascriptBrowser
private OAuthGrant oAuthGrantPage;
private JavascriptTestExecutorWithAuthorization testExecutor;
@FindBy(id = "output")
@JavascriptBrowser
protected WebElement outputArea;
@FindBy(id = "events")
@JavascriptBrowser
protected WebElement eventsArea;
@Override @Override
public void setDefaultPageUriParameters() { public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters(); super.setDefaultPageUriParameters();
testRealmPage.setAuthRealm(REALM_NAME); testRealmPage.setAuthRealm(REALM_NAME);
oAuthGrantPage.setAuthRealm(REALM_NAME);
} }
@Before @Before
public void beforePhotozExampleAdapterTest() throws Exception { public void beforePhotozExampleAdapterTest() throws Exception {
DroneUtils.addWebDriver(jsDriver); DroneUtils.addWebDriver(jsDriver);
deleteAllCookiesForClientPage();
this.deployer.deploy(RESOURCE_SERVER_ID); this.deployer.deploy(RESOURCE_SERVER_ID);
clientPage.navigateTo();
waitForPageToLoad();
assertCurrentUrlStartsWith(clientPage.toString());
testExecutor = JavascriptTestExecutorWithAuthorization.create(jsDriver, jsDriverTestRealmLoginPage);
clientPage.setTestExecutorPlayground(testExecutor, appServerContextRootPage + "/" + RESOURCE_SERVER_ID);
jsDriver.manage().deleteAllCookies();
try (CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build()) { try (CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build()) {
HttpGet request = new HttpGet(clientPage.toString() + "/unsecured/clean"); HttpGet request = new HttpGet(clientPage.toString() + "/unsecured/clean");
httpClient.execute(request).close(); httpClient.execute(request).close();
@ -159,18 +158,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
importResourceServerSettings(); importResourceServerSettings();
} }
//revert provider in persistence.xml for EAP6 backward compatibility
protected static void updatePersistenceXml(WebArchive war) {
//double check app server is eap6
if (AppServerTestEnricher.isEAP6AppServer()) {
String persistanceXmlPath = "/WEB-INF/classes/META-INF/persistence.xml";
org.w3c.dom.Document persistenceXml = IOUtil.loadXML(war.get(persistanceXmlPath).getAsset().openStream());
IOUtil.modifyDocElementValue(persistenceXml, "provider",
"org.hibernate.jpa.HibernatePersistenceProvider", "org.hibernate.ejb.HibernatePersistence");
war.add(new StringAsset((IOUtil.documentToString(persistenceXml))), persistanceXmlPath);
}
}
private List<ResourceRepresentation> getResourcesOfUser(String username) throws FileNotFoundException { private List<ResourceRepresentation> getResourcesOfUser(String username) throws FileNotFoundException {
return getAuthorizationResource().resources().resources().stream().filter(resource -> resource.getOwner().getName().equals(username)).collect(Collectors.toList()); return getAuthorizationResource().resources().resources().stream().filter(resource -> resource.getOwner().getName().equals(username)).collect(Collectors.toList());
} }
@ -178,50 +165,62 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
private void printUpdatedPolicies() throws FileNotFoundException { private void printUpdatedPolicies() throws FileNotFoundException {
log.debug("Check updated policies"); log.debug("Check updated policies");
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
log.debugf("Policy: {0}", policy.getName()); log.debugf("Policy: %s", policy.getName());
for (String key : policy.getConfig().keySet()) { for (String key : policy.getConfig().keySet()) {
log.debugf("-- key: {0}, value: {1}", key, policy.getConfig().get(key)); log.debugf("-- key: %s, value: %s", key, policy.getConfig().get(key));
} }
} }
log.debug("------------------------------"); log.debug("------------------------------");
} }
private void assertOnTestAppUrl(WebDriver jsDriver, Object output, WebElement events) {
waitForPageToLoad();
assertCurrentUrlStartsWith(clientPage.toString(), jsDriver);
}
private void assertWasDenied(Map<String, Object> response) {
assertThat(response.get("status")).isEqualTo(401L);
}
private void assertWasNotDenied(Map<String, Object> response) {
assertThat(response.get("status")).isEqualTo(200L);
}
@Test @Test
public void testUserCanCreateAndDeleteAlbum() throws Exception { public void testUserCanCreateAndDeleteAlbum() throws Exception {
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.createAlbum("Alice Family Album"); clientPage.createAlbum(ALICE_ALBUM_NAME);
log.debug("Check if alice has resources stored"); log.debug("Check if alice has resources stored");
assertThat(getResourcesOfUser("alice"), is(not(empty()))); assertThat(getResourcesOfUser("alice")).isNotEmpty();
clientPage.deleteAlbum("Alice Family Album", false); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
log.debug("Check if alice has resources deleted"); log.debug("Check if alice has resources deleted");
assertThat(getResourcesOfUser("alice"), is(empty())); assertThat(getResourcesOfUser("alice")).isEmpty();
} }
@Test @Test
public void createAlbumWithInvalidUser() throws Exception { public void createAlbumWithInvalidUser() throws Exception {
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.createAlbumWithInvalidUser("Alice Family Album"); clientPage.createAlbumWithInvalidUser(ALICE_ALBUM_NAME, response -> {
assertThat(response.get("status")).isEqualTo(500L);
log.debug("Check if the album was not created."); assertThat(response.get("res")).isEqualTo("Could not register protected resource.");
waitUntilElement(clientPage.getOutput()).text().not().contains("Request was successful"); });
waitUntilElement(clientPage.getOutput()).text().contains("Could not register protected resource");
} }
@Test @Test
public void testOnlyOwnerCanDeleteAlbum() throws Exception { public void testOnlyOwnerCanDeleteAlbum() throws Exception {
ContainerAssume.assumeNotAuthServerUndertow(); ContainerAssume.assumeNotAppServerUndertow();
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.createAlbum("Alice-Family-Album"); clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage("admin", "admin"); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
log.debug("Check if alice has resources stored"); log.debug("Check if alice has resources stored");
assertThat(getResourcesOfUser("alice"), is(not(empty()))); assertThat(getResourcesOfUser("alice")).isNotEmpty();
log.debug("Adding applyPolicies \"Only Owner Policy\" to \"Delete Album Permission\" policies."); log.debug("Adding applyPolicies \"Only Owner Policy\" to \"Delete Album Permission\" policies.");
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
@ -232,13 +231,13 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
printUpdatedPolicies(); printUpdatedPolicies();
loginToClientPage("admin", "admin"); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum("Alice-Family-Album", true); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
log.debug("Check if alice has resources stored"); log.debug("Check if alice has resources stored");
assertThat(getResourcesOfUser("alice"), is(not(empty()))); assertThat(getResourcesOfUser("alice")).isNotEmpty();
log.debug("Adding applyPolicies \"Only Owner and Administrators Policy\" to \"Delete Album Permission\" policies."); log.debug("Adding applyPolicies \"Only Owner and Administrators Policy\" to \"Delete Album Permission\" policies.");
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
@ -249,51 +248,52 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
printUpdatedPolicies(); printUpdatedPolicies();
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum("Alice-Family-Album", false); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
log.debug("Check if alice has resources deleted"); log.debug("Check if alice has resources deleted");
assertThat(getResourcesOfUser("alice"), is(empty())); assertThat(getResourcesOfUser("alice")).isEmpty();
} }
@Test @Test
public void testRegularUserCanNotAccessAdminResources() throws Exception { public void testRegularUserCanNotAccessAdminResources() throws Exception {
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.navigateToAdminAlbum(true); clientPage.navigateToAdminAlbum(this::assertWasDenied);
} }
@Test @Test
public void testAdminOnlyFromSpecificAddress() throws Exception { public void testAdminOnlyFromSpecificAddress() throws Exception {
ContainerAssume.assumeNotAuthServerUndertow(); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
loginToClientPage("admin", "admin");
clientPage.navigateToAdminAlbum(false);
log.debug("Changing codes \"127.0.0.1\" to \"127.3.3.3\" of \"Only From a Specific Client Address\" policies."); log.debug("Changing codes \"127.0.0.1\" to \"127.3.3.3\" of \"Only From a Specific Client Address\" policies.");
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Only From a Specific Client Address".equals(policy.getName())) { if ("Only From a Specific Client Address".equals(policy.getName())) {
String code = policy.getConfig().get("code"); String code = policy.getConfig().get("code")
policy.getConfig().put("code", code.replaceAll("127.0.0.1", "127.3.3.3")); .replaceAll("127.0.0.1", "127.3.3.3")
.replaceAll("0:0:0:0:0:0:0:1", "0:0:0:0:0:ffff:7f03:303");
policy.getConfig().put("code", code);
getAuthorizationResource().policies().policy(policy.getId()).update(policy); getAuthorizationResource().policies().policy(policy.getId()).update(policy);
} }
} }
printUpdatedPolicies(); printUpdatedPolicies();
clientPage.navigateToAdminAlbum(true); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasDenied);
} }
@Test @Test
public void testAdminWithoutPermissionsToTypedResource() throws Exception { public void testAdminWithoutPermissionsToTypedResource() throws Exception {
ContainerAssume.assumeNotAuthServerUndertow(); ContainerAssume.assumeNotAppServerUndertow();
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.createAlbum("Alice Family Album"); clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage("admin", "admin"); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum("Alice Family Album", false); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Album Resource Permission".equals(policy.getName())) { if ("Album Resource Permission".equals(policy.getName())) {
@ -315,11 +315,13 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
printUpdatedPolicies(); printUpdatedPolicies();
clientPage.navigateToAdminAlbum(false); loginToClientPage(adminUser); // Clear cache
clientPage.viewAlbum("Alice Family Album", true); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.navigateToAdminAlbum(false);
clientPage.deleteAlbum("Alice Family Album", true); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Album Resource Permission".equals(policy.getName())) { if ("Album Resource Permission".equals(policy.getName())) {
@ -329,26 +331,27 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
printUpdatedPolicies(); printUpdatedPolicies();
clientPage.navigateToAdminAlbum(false); loginToClientPage(adminUser); // Clear cache
clientPage.viewAlbum("Alice Family Album", false);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum("Alice Family Album", false); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice"), is(empty()));
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice")).isEmpty();
} }
@Test @Test
public void testAdminWithoutPermissionsToDeleteAlbum() throws Exception { public void testAdminWithoutPermissionsToDeleteAlbum() throws Exception {
ContainerAssume.assumeNotAuthServerUndertow(); loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage("alice", "alice"); loginToClientPage(adminUser);
clientPage.createAlbum("Alice Family Album"); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
loginToClientPage("admin", "admin"); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.navigateToAdminAlbum(false); assertThat(getResourcesOfUser("alice")).isEmpty();
clientPage.deleteAlbum("Alice Family Album", false);
assertThat(getResourcesOfUser("alice"), is(empty()));
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Delete Album Permission".equals(policy.getName())) { if ("Delete Album Permission".equals(policy.getName())) {
@ -358,16 +361,16 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
printUpdatedPolicies(); printUpdatedPolicies();
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.createAlbum("Alice Family Album"); clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage("admin", "admin"); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum("Alice Family Album", false); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice"), is(not(empty()))); assertThat(getResourcesOfUser("alice")).isNotEmpty();
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum("Alice Family Album", true); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Delete Album Permission".equals(policy.getName())) { if ("Delete Album Permission".equals(policy.getName())) {
@ -377,18 +380,18 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
printUpdatedPolicies(); printUpdatedPolicies();
clientPage.navigateToAdminAlbum(false); loginToClientPage(adminUser); // Clear cache
clientPage.deleteAlbum("Alice Family Album", false);
assertThat(getResourcesOfUser("alice"), is(empty())); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice")).isEmpty();
} }
@Test @Test
public void testClientRoleRepresentingUserConsent() throws Exception { public void testClientRoleRepresentingUserConsent() throws Exception {
ContainerAssume.assumeNotAuthServerUndertow(); loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage("alice", "alice"); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.createAlbum("Alice Family Album");
clientPage.viewAlbum("Alice Family Album", false);
RealmResource realmResource = realmsResouce().realm(REALM_NAME); RealmResource realmResource = realmsResouce().realm(REALM_NAME);
UsersResource usersResource = realmResource.users(); UsersResource usersResource = realmResource.users();
@ -406,19 +409,19 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
setManageAlbumScopeRequired(); setManageAlbumScopeRequired();
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.viewAlbum("Alice Family Album", true); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage("alice", "alice", "manage-albums"); loginToClientPage(aliceUser, "manage-albums");
clientPage.viewAlbum("Alice Family Album", false); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
} }
@Test @Test
public void testClientRoleNotRequired() throws Exception { public void testClientRoleNotRequired() throws Exception {
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.createAlbum("Alice Family Album"); clientPage.createAlbum(ALICE_ALBUM_NAME);
clientPage.viewAlbum("Alice Family Album", false); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
UsersResource usersResource = realmsResouce().realm(REALM_NAME).users(); UsersResource usersResource = realmsResouce().realm(REALM_NAME).users();
List<UserRepresentation> users = usersResource.search("alice", null, null, null, null, null); List<UserRepresentation> users = usersResource.search("alice", null, null, null, null, null);
@ -440,8 +443,8 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
manageAlbumRole.update(roleRepresentation); manageAlbumRole.update(roleRepresentation);
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.viewAlbum("Alice Family Album", true); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Any User Policy".equals(policy.getName())) { if ("Any User Policy".equals(policy.getName())) {
@ -460,34 +463,28 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
printUpdatedPolicies(); printUpdatedPolicies();
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.viewAlbum("Alice Family Album", false); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
} }
@Test @Test
public void testOverridePermissionFromResourceParent() throws Exception { public void testOverridePermissionFromResourceParent() throws Exception {
ContainerAssume.assumeNotAuthServerUndertow(); loginToClientPage(aliceUser);
String resourceName = "My-Resource-Instance";
loginToClientPage("alice", "alice");
String resourceName = "My Resource Instance";
clientPage.createAlbum(resourceName); clientPage.createAlbum(resourceName);
clientPage.viewAlbum(resourceName, false); clientPage.viewAlbum(resourceName, this::assertWasNotDenied);
clientPage.deleteAlbum(resourceName, this::assertWasNotDenied);
clientPage.navigateTo();
clientPage.deleteAlbum(resourceName, false);
clientPage.createAlbum(resourceName); clientPage.createAlbum(resourceName);
loginToClientPage("admin", "admin"); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(resourceName, false); clientPage.viewAlbum(resourceName, this::assertWasNotDenied);
clientPage.deleteAlbum(resourceName, this::assertWasNotDenied);
clientPage.navigateToAdminAlbum(false); loginToClientPage(aliceUser);
clientPage.deleteAlbum(resourceName, false);
loginToClientPage("alice", "alice");
clientPage.createAlbum(resourceName); clientPage.createAlbum(resourceName);
getAuthorizationResource().resources().resources().forEach(resource -> { getAuthorizationResource().resources().resources().forEach(resource -> {
@ -512,54 +509,45 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}); });
printUpdatedPolicies(); printUpdatedPolicies();
loginToClientPage("admin", "admin"); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(resourceName, true); clientPage.viewAlbum(resourceName, this::assertWasDenied);
clientPage.deleteAlbum(resourceName, this::assertWasDenied);
clientPage.navigateToAdminAlbum(false); loginToClientPage(aliceUser);
clientPage.deleteAlbum(resourceName, true); clientPage.deleteAlbum(resourceName, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice")).isEmpty();
loginToClientPage("alice", "alice");
clientPage.deleteAlbum(resourceName, false);
assertThat(getResourcesOfUser("alice"), is(empty()));
} }
@Test @Test
public void testInheritPermissionFromResourceParent() throws Exception { public void testInheritPermissionFromResourceParent() throws Exception {
ContainerAssume.assumeNotAuthServerUndertow(); ContainerAssume.assumeNotAppServerUndertow();
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
String resourceName = "My Resource Instance"; final String RESOURCE_NAME = "My-Resource-Instance";
clientPage.createAlbum(resourceName); clientPage.createAlbum(RESOURCE_NAME);
clientPage.viewAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.viewAlbum(resourceName, false); clientPage.createAlbum(RESOURCE_NAME);
clientPage.navigateTo(); loginToClientPage(adminUser);
clientPage.deleteAlbum(resourceName, false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.createAlbum(resourceName); loginToClientPage(aliceUser);
clientPage.createAlbum(RESOURCE_NAME);
loginToClientPage("admin", "admin");
clientPage.navigateToAdminAlbum(false);
clientPage.viewAlbum(resourceName, false);
clientPage.navigateToAdminAlbum(false);
clientPage.deleteAlbum(resourceName, false);
loginToClientPage("alice", "alice");
clientPage.createAlbum(resourceName);
ResourcesResource resourcesResource = getAuthorizationResource().resources(); ResourcesResource resourcesResource = getAuthorizationResource().resources();
resourcesResource.resources().forEach(resource -> { resourcesResource.resources().forEach(resource -> {
if (resource.getName().equals(resourceName)) { if (resource.getName().equals(RESOURCE_NAME)) {
try { try {
PolicyRepresentation resourceInstancePermission = new PolicyRepresentation(); PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
resourceInstancePermission.setName(resourceName + "Permission"); resourceInstancePermission.setName(RESOURCE_NAME + "Permission");
resourceInstancePermission.setType("resource"); resourceInstancePermission.setType("resource");
Map<String, String> config = new HashMap<>(); Map<String, String> config = new HashMap<>();
@ -575,140 +563,151 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
} }
}); });
loginToClientPage("admin", "admin"); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(resourceName, true); clientPage.viewAlbum(RESOURCE_NAME, this::assertWasDenied);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasDenied);
clientPage.navigateToAdminAlbum(false);
clientPage.deleteAlbum(resourceName, true);
resourcesResource.resources().forEach(resource -> { resourcesResource.resources().forEach(resource -> {
if (resource.getName().equals(resourceName)) { if (resource.getName().equals(RESOURCE_NAME)) {
resource.setScopes(resource.getScopes().stream().filter(scope -> !scope.getName().equals("album:view")).collect(Collectors.toSet())); resource.setScopes(resource.getScopes().stream().filter(scope -> !scope.getName().equals("album:view")).collect(Collectors.toSet()));
resourcesResource.resource(resource.getId()).update(resource); resourcesResource.resource(resource.getId()).update(resource);
} }
}); });
loginToClientPage("admin", "admin"); loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(false); clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(resourceName, false); clientPage.viewAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasDenied);
clientPage.navigateToAdminAlbum(false); loginToClientPage(aliceUser);
clientPage.deleteAlbum(resourceName, true); clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasNotDenied);
loginToClientPage("alice", "alice");
clientPage.deleteAlbum(resourceName, false);
List<ResourceRepresentation> resources = resourcesResource.resources(); List<ResourceRepresentation> resources = resourcesResource.resources();
assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty()); assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
resourcesResource.resources().forEach(resource -> {
if (resource.getName().equals(resourceName)) {
resource.setScopes(Collections.emptySet());
resourcesResource.resource(resource.getId()).update(resource);
}
});
} }
//KEYCLOAK-3777 //KEYCLOAK-3777
@Test @Test
public void testEntitlementRequest() throws Exception { public void testEntitlementRequest() throws Exception {
ContainerAssume.assumeNotAuthServerUndertow(); loginToClientPage(adminUser);
clientPage.navigateTo(); clientPage.requestEntitlements((driver1, output, events) -> assertThat((String)output).contains("admin:manage"));
loginToClientPage("admin", "admin");
clientPage.requestEntitlements(); loginToClientPage(adminUser);
assertTrue(jsDriver.getPageSource().contains("admin:manage")); clientPage.requestEntitlement((driver1, output, events) -> assertThat((String)output)
.doesNotContain("admin:manage")
clientPage.requestEntitlement(); .contains("album:view")
String pageSource = jsDriver.getPageSource(); .contains("album:delete")
assertTrue(pageSource.contains("album:view")); );
assertTrue(pageSource.contains("album:delete"));
} }
@Test @Test
public void testResourceProtectedWithAnyScope() throws Exception { public void testResourceProtectedWithAnyScope() throws Exception {
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.requestResourceProtectedAllScope(true);
clientPage.requestResourceProtectedAnyScope(false); clientPage.requestResourceProtectedAllScope(this::assertWasDenied);
clientPage.requestResourceProtectedAnyScope(response -> {
assertThat(response.get("status")).isIn(404L, 0L); // PhantomJS returns 0 and chrome 404
});
} }
@Test @Test
public void testRequestResourceToOwner() throws Exception { public void testRequestResourceToOwner() throws Exception {
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.createAlbum("Alice-Family-Album", true); clientPage.createAlbum(ALICE_ALBUM_NAME, true);
loginToClientPage("jdoe", "jdoe"); loginToClientPage(jdoeUser);
clientPage.viewAllAlbums(); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.viewAlbum("Alice-Family-Album", true); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.accountGrantResource(ALICE_ALBUM_NAME, "jdoe");
// get back to clientPage and init javascript adapter in order to log out correctly
clientPage.navigateTo(); clientPage.navigateTo();
clientPage.viewAllAlbums();
clientPage.deleteAlbum("Alice-Family-Album", true);
loginToClientPage("alice", "alice"); testExecutor.init(defaultArguments(), this::assertInitNotAuth)
clientPage.accountGrantResource("Alice-Family-Album", "jdoe"); .login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage("jdoe", "jdoe"); loginToClientPage(jdoeUser);
clientPage.viewAllAlbums(); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.viewAlbum("Alice-Family-Album", false); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME, true);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.accountGrantRemoveScope(ALICE_ALBUM_NAME, "jdoe", "album:delete");
// get back to clientPage and init javascript adapter in order to navigate to accountPage again
clientPage.navigateTo(); clientPage.navigateTo();
clientPage.viewAllAlbums(); testExecutor.init(defaultArguments(), this::assertInitNotAuth)
clientPage.deleteAlbum("Alice-Family-Album", false); .login(this::assertOnTestAppUrl)
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
clientPage.accountGrantResource(ALICE_ALBUM_NAME, "jdoe");
loginToClientPage("alice", "alice"); // get back to clientPage and init javascript adapter in order to log out correctly
clientPage.createAlbum("Alice-Family-Album", true);
loginToClientPage("jdoe", "jdoe");
clientPage.viewAllAlbums();
clientPage.viewAlbum("Alice-Family-Album", true);
clientPage.navigateTo(); clientPage.navigateTo();
clientPage.viewAllAlbums(); testExecutor.init(defaultArguments(), this::assertInitNotAuth)
clientPage.deleteAlbum("Alice-Family-Album", true); .login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage("alice", "alice");
clientPage.accountGrantRemoveScope("Alice-Family-Album", "jdoe", "album:delete");
clientPage.accountGrantResource("Alice-Family-Album", "jdoe");
loginToClientPage("jdoe", "jdoe"); loginToClientPage(jdoeUser);
clientPage.viewAllAlbums(); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.viewAlbum("Alice-Family-Album", false); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.navigateTo();
clientPage.viewAllAlbums();
clientPage.deleteAlbum("Alice-Family-Album", true);
} }
@Test @Test
public void testOwnerSharingResource() throws Exception { public void testOwnerSharingResource() throws Exception {
loginToClientPage("alice", "alice"); loginToClientPage(aliceUser);
clientPage.createAlbum("Alice-Family-Album", true); clientPage.createAlbum(ALICE_ALBUM_NAME, true);
clientPage.accountShareResource("Alice-Family-Album", "jdoe"); clientPage.accountShareResource(ALICE_ALBUM_NAME, "jdoe");
loginToClientPage("jdoe", "jdoe"); // get back to clientPage and init javascript adapter in order to log out correctly
clientPage.viewAllAlbums();
clientPage.viewAlbum("Alice-Family-Album", false);
clientPage.navigateTo(); clientPage.navigateTo();
clientPage.viewAllAlbums(); testExecutor.init(defaultArguments(), this::assertInitNotAuth)
clientPage.deleteAlbum("Alice-Family-Album", false); .login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage("alice", "alice"); loginToClientPage(jdoeUser);
clientPage.createAlbum("Alice-Family-Album", true); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.accountShareRemoveScope("Alice-Family-Album", "jdoe", "album:delete"); clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
loginToClientPage("jdoe", "jdoe"); loginToClientPage(aliceUser);
clientPage.viewAllAlbums(); clientPage.createAlbum(ALICE_ALBUM_NAME, true);
clientPage.viewAlbum("Alice-Family-Album", false); clientPage.accountShareRemoveScope(ALICE_ALBUM_NAME, "jdoe", "album:delete");
// get back to clientPage and init javascript adapter in order to log out correctly
clientPage.navigateTo(); clientPage.navigateTo();
clientPage.viewAllAlbums(); testExecutor.init(defaultArguments(), this::assertInitNotAuth)
clientPage.deleteAlbum("Alice-Family-Album", true); .login(this::assertOnTestAppUrl)
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage("alice", "alice"); loginToClientPage(jdoeUser);
clientPage.accountRevokeResource("Alice-Family-Album", "jdoe"); clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage("jdoe", "jdoe"); loginToClientPage(aliceUser);
clientPage.viewAllAlbums(); clientPage.accountRevokeResource(ALICE_ALBUM_NAME, "jdoe");
clientPage.viewAlbum("Alice-Family-Album", true);
// get back to clientPage and init javascript adapter in order to log out correctly
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
} }
private void importResourceServerSettings() throws FileNotFoundException { private void importResourceServerSettings() throws FileNotFoundException {
@ -731,41 +730,23 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
return clients.get(resourceServer.getId()); return clients.get(resourceServer.getId());
} }
private void deleteAllCookiesForClientPage() { private void loginToClientPage(UserRepresentation user, String... scopes) throws InterruptedException {
log.debugf("--logging in as {0} with password: {1}; scopes: {2}", user.getUsername(), user.getCredentials().get(0).getValue(), Arrays.toString(scopes));
if (testExecutor.isLoggedIn()) {
testExecutor.logout(this::assertOnTestAppUrl);
jsDriver.manage().deleteAllCookies();
jsDriver.navigate().to(testRealmLoginPage.toString());
waitForPageToLoad();
jsDriver.manage().deleteAllCookies(); jsDriver.manage().deleteAllCookies();
} }
private void loginToClientPage(String username, String password, String... scopes) throws InterruptedException {
log.debugf("--logging in as {0} with password: {1}; scopes: {2}", username, password, Arrays.toString(scopes));
clientPage.navigateTo(); clientPage.navigateTo();
if (jsDriver.getCurrentUrl().startsWith(clientPage.toString())) { testExecutor.init(defaultArguments(), this::assertInitNotAuth)
try { .login(this::assertOnLoginPage)
clientPage.logOut(); .loginFormWithScopesWithPossibleConsentPage(user, this::assertOnTestAppUrl, oAuthGrantPage, scopes)
} catch (NoSuchElementException ex) { .init(defaultArguments(), this::assertSuccessfullyLoggedIn);
if ("phantomjs".equals(System.getProperty("js.browser"))) {
// PhantomJS is broken, it can't logout using sign out button sometimes, we have to clean sessions and remove cookies
adminClient.realm(REALM_NAME).logoutAll();
jsDriverTestRealmLoginPage.navigateTo();
driver.manage().deleteAllCookies();
clientPage.navigateTo();
driver.manage().deleteAllCookies();
clientPage.navigateTo();
// Check for correct logout
assertCurrentUrlStartsWithLoginUrlOf(jsDriverTestRealmLoginPage);
} else {
throw ex;
}
}
}
clientPage.navigateTo();
waitForPageToLoad();
clientPage.login(username, password, scopes);
waitUntilElement(By.linkText("Sign Out")).is().clickable();
} }
private void setManageAlbumScopeRequired() { private void setManageAlbumScopeRequired() {

View file

@ -0,0 +1,108 @@
package org.keycloak.testsuite.adapter.example.authorization;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.javascript.AbstractJavascriptTest;
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.testsuite.util.javascript.JSObjectBuilder;
import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
import org.keycloak.testsuite.util.javascript.ResponseValidator;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import static org.hamcrest.CoreMatchers.containsString;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
/**
* @author mhajas
*/
public abstract class AbstractPhotozJavascriptExecutorTest extends AbstractExampleAdapterTest {
@FunctionalInterface
interface QuadFunction<T, U, V, W> {
void apply(T a, U b, V c, W d);
}
protected static final String REALM_NAME = "photoz";
@Page
@JavascriptBrowser
protected OIDCLogin jsDriverTestRealmLoginPage;
@Page
@JavascriptBrowser
private OAuthGrant oAuthGrantPage;
@Drone
@JavascriptBrowser
protected WebDriver jsDriver;
protected UserRepresentation aliceUser = UserBuilder.create().username("alice").password("alice").build();
protected UserRepresentation adminUser = UserBuilder.create().username("admin").password("admin").build();
protected UserRepresentation jdoeUser = UserBuilder.create().username("jdoe").password("jdoe").build();
@Before
public void setDefaultValues() {
jsDriverTestRealmLoginPage.setAuthRealm(REALM_NAME);
}
protected <T> JavascriptStateValidator buildFunction(QuadFunction<T, WebDriver, Object, WebElement> f, T x) {
return (y,z,w) -> f.apply(x, y, z, w);
}
public void assertOutputContains(String value, WebDriver driver1, Object output, WebElement events) {
if (output instanceof WebElement) {
waitUntilElement((WebElement) output).text().contains(value);
} else {
Assert.assertThat((String) output, containsString(value));
}
}
protected JSObjectBuilder defaultArguments() {
return JSObjectBuilder.create().defaultSettings();
}
protected void assertSuccessfullyLoggedIn(WebDriver driver1, Object output, WebElement events) {
buildFunction(this::assertOutputContains, "Init Success (Authenticated)").validate(driver1, output, events);
}
protected void assertInitNotAuth(WebDriver driver1, Object output, WebElement events) {
buildFunction(this::assertOutputContains, "Init Success (Not Authenticated)").validate(driver1, output, events);
}
protected void assertOnLoginPage(WebDriver driver1, Object output, WebElement events) {
waitUntilElement(By.tagName("body")).is().present();
try {
assertCurrentUrlStartsWith(jsDriverTestRealmLoginPage, driver1);
} catch (AssertionError e) {
System.out.println("Test");
throw e;
}
}
protected JavascriptStateValidator all(JavascriptStateValidator[] toValidate) {
return ((driver1, output, events) -> {
for (JavascriptStateValidator val : toValidate) {
val.validate(driver1, output, events);
}
});
}
protected ResponseValidator all(ResponseValidator[] toValidate) {
return ((response) -> {
for (ResponseValidator val : toValidate) {
val.validate(response);
}
});
}
}

View file

@ -34,6 +34,7 @@ import org.keycloak.testsuite.arquillian.containers.ContainerConstants;
@AppServerContainer(ContainerConstants.APP_SERVER_EAP) @AppServerContainer(ContainerConstants.APP_SERVER_EAP)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6) @AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71) @AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
public class PhotozExampleLazyLoadPathsAdapterTest extends AbstractPhotozExampleAdapterTest { public class PhotozExampleLazyLoadPathsAdapterTest extends AbstractPhotozExampleAdapterTest {
@Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME) @Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
@ -43,15 +44,7 @@ public class PhotozExampleLazyLoadPathsAdapterTest extends AbstractPhotozExample
@Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false) @Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
public static WebArchive deploymentResourceServer() throws IOException { public static WebArchive deploymentResourceServer() throws IOException {
WebArchive war = exampleDeployment(RESOURCE_SERVER_ID) return exampleDeployment(RESOURCE_SERVER_ID)
.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/photoz/keycloak-lazy-load-path-authz-service.json"), "keycloak.json"); .addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/photoz/keycloak-lazy-load-path-authz-service.json"), "keycloak.json");
// cannot use EAP6DeploymentArchiveProcessor because the deployment is marked
// as non managed by arquillian
if (AppServerTestEnricher.isEAP6AppServer()) {
updatePersistenceXml(war);
} }
return war;
}
} }

View file

@ -33,6 +33,7 @@ import org.keycloak.testsuite.arquillian.containers.ContainerConstants;
@AppServerContainer(ContainerConstants.APP_SERVER_EAP) @AppServerContainer(ContainerConstants.APP_SERVER_EAP)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6) @AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71) @AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
public class PhotozExampleNoLazyLoadPathsAdapterTest extends AbstractPhotozExampleAdapterTest { public class PhotozExampleNoLazyLoadPathsAdapterTest extends AbstractPhotozExampleAdapterTest {
@Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME) @Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
@ -42,14 +43,7 @@ public class PhotozExampleNoLazyLoadPathsAdapterTest extends AbstractPhotozExamp
@Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false) @Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
public static WebArchive deploymentResourceServer() throws IOException { public static WebArchive deploymentResourceServer() throws IOException {
WebArchive war = exampleDeployment(RESOURCE_SERVER_ID); return exampleDeployment(RESOURCE_SERVER_ID);
// cannot use EAP6DeploymentArchiveProcessor because the deployment is marked
// as non managed by arquillian
if (AppServerTestEnricher.isEAP6AppServer()) {
updatePersistenceXml(war);
}
return war;
} }
} }

View file

@ -17,6 +17,8 @@ import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RolesBuilder; import org.keycloak.testsuite.util.RolesBuilder;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
import org.keycloak.testsuite.util.javascript.ResponseValidator;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.Cookie; import org.openqa.selenium.Cookie;
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.JavascriptExecutor;

View file

@ -24,6 +24,9 @@ import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.testsuite.util.javascript.JSObjectBuilder;
import org.keycloak.testsuite.util.javascript.JavascriptTestExecutor;
import org.keycloak.testsuite.util.javascript.XMLHttpRequest;
import org.openqa.selenium.TimeoutException; import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebDriverException;
@ -83,10 +86,10 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
applicationsPage.setAuthRealm(REALM_NAME); applicationsPage.setAuthRealm(REALM_NAME);
jsDriver.navigate().to(testAppUrl); jsDriver.navigate().to(testAppUrl);
testExecutor = JavascriptTestExecutor.create(jsDriver, jsDriverTestRealmLoginPage);
waitUntilElement(outputArea).is().present(); waitUntilElement(outputArea).is().present();
assertCurrentUrlStartsWith(testAppUrl, jsDriver); assertCurrentUrlStartsWith(testAppUrl, jsDriver);
testExecutor = JavascriptTestExecutor.create(jsDriver, jsDriverTestRealmLoginPage);
jsDriver.manage().deleteAllCookies(); jsDriver.manage().deleteAllCookies();
@ -168,6 +171,8 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
@Test @Test
public void grantBrowserBasedApp() { public void grantBrowserBasedApp() {
Assume.assumeTrue("This test doesn't work with phantomjs", !"phantomjs".equals(System.getProperty("js.browser")));
ClientResource clientResource = ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), CLIENT_ID); ClientResource clientResource = ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), CLIENT_ID);
ClientRepresentation client = clientResource.toRepresentation(); ClientRepresentation client = clientResource.toRepresentation();
client.setConsentRequired(true); client.setConsentRequired(true);
@ -403,7 +408,7 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
List<UserRepresentation> users = adminClient.realm(REALM_NAME).users().search("mhajas", 0, 1); List<UserRepresentation> users = adminClient.realm(REALM_NAME).users().search("mhajas", 0, 1);
assertEquals("There should be created user mhajas", 1, users.size()); assertEquals("There should be created user mhajas", 1, users.size());
assertThat((String) response.get("responseHeaders"), containsString("location: " + authServerContextRootPage.toString() + "/auth/admin/realms/" + REALM_NAME + "/users/" + users.get(0).getId())); assertThat(((String) response.get("responseHeaders")).toLowerCase(), containsString("location: " + authServerContextRootPage.toString() + "/auth/admin/realms/" + REALM_NAME + "/users/" + users.get(0).getId()));
}); });
} }

View file

@ -75,7 +75,7 @@ public class UndertowDeployerHelper {
di = new DeploymentInfo(); di = new DeploymentInfo();
} }
UndertowWarClassLoader classLoader = new UndertowWarClassLoader(UndertowDeployerHelper.class.getClassLoader(), archive); UndertowWarClassLoader classLoader = new UndertowWarClassLoader(Thread.currentThread().getContextClassLoader(), archive);
di.setClassLoader(classLoader); di.setClassLoader(classLoader);
di.setDeploymentName(archiveName); di.setDeploymentName(archiveName);