diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
index 8f42e66463..6d30cdf331 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
@@ -94,6 +94,24 @@ body {
margin-left: -2.27272727272727em;
padding-top: 2.90909090909091em;
}
+.error-container {
+ width: 54em;
+ margin-top: 6em;
+ margin-left: 8em;
+}
+.error-container h2 {
+ text-transform: uppercase;
+ font-size: 2.3em;
+ font-family: Overpass, sans-serif;
+ margin-bottom: 1.52173913043478em;
+}
+.error-container p.instruction {
+ font-size: 1.3em;
+}
+.error-container .link-right {
+ float: right;
+ font-size: 1.3em;
+}
/* Header */
.header.rcue {
z-index: 50;
@@ -648,6 +666,7 @@ table.list tbody tr.expanded .form-actions {
.header.rcue .navbar.utility .navbar-inner,
.header.rcue .navbar.primary .navbar-inner {
max-width: 970px;
+ min-width: 560px;
}
#container-right-bg {
margin-left: 242.5px;
@@ -661,13 +680,17 @@ table.list tbody tr.expanded .form-actions {
}
.bs-sidebar,
.user .bs-sidebar {
- padding-top: 1em;
width: 100%;
+ padding-top: 0;
}
.bs-sidebar ul li,
.user .bs-sidebar ul li {
margin-left: 0;
}
+ .bs-sidebar ul li:first-child,
+ .user .bs-sidebar ul li:first-child {
+ margin-top: 1em;
+ }
.bs-sidebar ul li a,
.user .bs-sidebar ul li a {
border-width: 1px;
@@ -704,8 +727,16 @@ table.list tbody tr.expanded .form-actions {
#content-area {
border: none;
}
+ .bs-sidebar + #content-area {
+ margin-top: 0;
+ }
#container-right-bg {
border: none;
width: 100%;
}
+ .error-container {
+ width: inherit;
+ margin-right: 6em;
+ margin-left: 6em;
+ }
}
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
index 527e707bf3..1f49e268a3 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
@@ -117,6 +117,28 @@ body {
}
}
+.error-container {
+ width: 54em;
+ margin-top: 6em;
+ margin-left: 8em;
+
+ h2 {
+ text-transform: uppercase;
+ font-size: 2.3em;
+ font-family: Overpass, sans-serif;
+ margin-bottom: 1.52173913043478em;
+ }
+
+ p.instruction {
+ font-size: 1.3em;
+ }
+
+ .link-right {
+ float: right;
+ font-size: 1.3em;
+ }
+}
+
/* Header */
@@ -808,6 +830,7 @@ table.list {
.navbar.utility .navbar-inner,
.navbar.primary .navbar-inner {
max-width: 970px;
+ min-width: 560px;
}
}
@@ -829,13 +852,17 @@ table.list {
.bs-sidebar,
.user .bs-sidebar {
- padding-top: 1em;
width: 100%;
+ padding-top: 0;
ul li {
margin-left: 0;
+ &:first-child {
+ margin-top: 1em;
+ }
+
a {
border-width: 1px;
padding-left: 1.53846153846154em;
@@ -889,8 +916,18 @@ table.list {
border: none;
}
+ .bs-sidebar + #content-area {
+ margin-top: 0;
+ }
+
#container-right-bg {
border: none;
width: 100%;
}
+
+ .error-container {
+ width: inherit;
+ margin-right: 6em;
+ margin-left: 6em;
+ }
}
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
index c261b4836c..f0e6ddde66 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
@@ -253,7 +253,18 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ApplicationScopeMappingCtrl'
})
-
+ .when('/realms/:realm/applications/:application/installation', {
+ templateUrl : 'partials/application-installation.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ installation : function(ApplicationInstallationLoader) {
+ return ApplicationInstallationLoader();
+ }
+ },
+ controller : 'ApplicationInstallationCtrl'
+ })
.when('/create/application/:realm', {
templateUrl : 'partials/application-detail.html',
resolve : {
@@ -502,8 +513,8 @@ module.directive('onoffswitch', function() {
offText: '@offText'
},
compile: function(element, attrs) {
- if (!attrs.onText) { attrs.onText = "YES"; }
- if (!attrs.offText) { attrs.offText = "NO"; }
+ if (!attrs.onText) { attrs.onText = "ON"; }
+ if (!attrs.offText) { attrs.offText = "OFF"; }
var html = "
" +
"
" +
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
index c04d81fb96..ed435eeff1 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
@@ -179,6 +179,12 @@ module.controller('ApplicationListCtrl', function($scope, realm, applications, A
});
});
+module.controller('ApplicationInstallationCtrl', function($scope, realm, installation, ApplicationInstallation, $routeParams) {
+ $scope.realm = realm;
+ $scope.installation = installation;
+ $scope.download = ApplicationInstallation.url({ realm: $routeParams.realm, application: $routeParams.application });
+});
+
module.controller('ApplicationDetailCtrl', function($scope, realm, application, Application, $location, Dialog, Notifications) {
console.log('ApplicationDetailCtrl');
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/loaders.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/loaders.js
index cf1b57eabc..7b72c48504 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/loaders.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/loaders.js
@@ -87,6 +87,15 @@ module.factory('ApplicationRoleLoader', function(Loader, ApplicationRole, $route
});
});
+module.factory('ApplicationInstallationLoader', function(Loader, ApplicationInstallation, $route, $q) {
+ return Loader.get(ApplicationInstallation, function() {
+ return {
+ realm : $route.current.params.realm,
+ application : $route.current.params.application
+ }
+ });
+});
+
module.factory('ApplicationRoleListLoader', function(Loader, ApplicationRole, $route, $q) {
return Loader.query(ApplicationRole, function() {
return {
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
index b692c30051..88d4ec3713 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
@@ -15,9 +15,16 @@ module.service('Auth', function() {
module.service('Dialog', function($dialog) {
var dialog = {};
+
+ var escapeHtml = function(str) {
+ var div = document.createElement('div');
+ div.appendChild(document.createTextNode(str));
+ return div.innerHTML;
+ };
+
dialog.confirmDelete = function(name, type, success) {
- var title = 'Delete ' + type.charAt(0).toUpperCase() + type.slice(1);
- var msg = '
Are you sure you want to permanently delete the ' + type + ' "' + name + '"?' +
+ var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
+ var msg = '
Are you sure you want to permanently delete the ' + escapeHtml(type) + ' "' + escapeHtml(name) + '"?' +
'
This action can\'t be undone.';
var btns = [ {
result : 'cancel',
@@ -202,6 +209,22 @@ module.factory('Application', function($resource) {
});
});
+module.factory('ApplicationInstallation', function($resource) {
+ var url = '/auth-server/rest/saas/admin/realms/:realm/applications/:application/installation';
+ var resource = $resource('/auth-server/rest/saas/admin/realms/:realm/applications/:application/installation', {
+ realm : '@realm',
+ application : '@application'
+ }, {
+ update : {
+ method : 'PUT'
+ }
+ });
+ resource.url = function(parameters) {
+ return url.replace(':realm', parameters.realm).replace(':application', parameters.application);
+ }
+ return resource;
+});
+
module.factory('ApplicationCredentials', function($resource) {
return $resource('/auth-server/rest/saas/admin/realms/:realm/applications/:application/credentials', {
realm : '@realm',
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
index 0f46176ff8..fd7bb6d199 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
@@ -6,7 +6,7 @@
- Settings
- Credentials
- - Installation
+ - Installation
- Roles
- Scope
- Sessions
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
index 78def73721..2a21202efc 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
@@ -6,7 +6,7 @@
- Settings
- Credentials
- - Installation
+ - Installation
- Roles
- Scope
- Sessions
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-installation.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-installation.html
new file mode 100755
index 0000000000..a63d5528b5
--- /dev/null
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-installation.html
@@ -0,0 +1,27 @@
+
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
index 063ab8480d..5101392461 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
@@ -6,7 +6,7 @@
- Settings
- Credentials
- - Installation
+ - Installation
- Roles
- Scope
- Sessions
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-list.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-list.html
index 904cb67fa5..1093a3f7fd 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-list.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-list.html
@@ -6,7 +6,7 @@
- Settings
- Credentials
- - Installation
+ - Installation
- Roles
- Scope
- Sessions
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/notfound.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/notfound.html
index 12b2139832..64c6566624 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/notfound.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/notfound.html
@@ -1,5 +1,14 @@
- Page not found
+
+
+
+
Page not found...
+
We could not find the page you are looking for. Please make sure the URL you entered is correct.
+
Go to the home page »
+
+
+
+
\ No newline at end of file
diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationInstallationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationInstallationRepresentation.java
new file mode 100755
index 0000000000..fbad6df3e5
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationInstallationRepresentation.java
@@ -0,0 +1,80 @@
+package org.keycloak.representations.idm;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class ApplicationInstallationRepresentation {
+ protected String realm;
+ protected String resource;
+ @JsonProperty("realm-public-key")
+ protected String realmPublicKey;
+ @JsonProperty("auth-url")
+ protected String authUrl;
+ @JsonProperty("code-url")
+ protected String codeUrl;
+ @JsonProperty("ssl-not-required")
+ protected boolean sslNotRequired;
+ protected Map credentials;
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ public String getResource() {
+ return resource;
+ }
+
+ public void setResource(String resource) {
+ this.resource = resource;
+ }
+
+ public String getRealmPublicKey() {
+ return realmPublicKey;
+ }
+
+ public void setRealmPublicKey(String realmPublicKey) {
+ this.realmPublicKey = realmPublicKey;
+ }
+
+ public String getAuthUrl() {
+ return authUrl;
+ }
+
+ public void setAuthUrl(String authUrl) {
+ this.authUrl = authUrl;
+ }
+
+ public String getCodeUrl() {
+ return codeUrl;
+ }
+
+ public void setCodeUrl(String codeUrl) {
+ this.codeUrl = codeUrl;
+ }
+
+ public boolean isSslNotRequired() {
+ return sslNotRequired;
+ }
+
+ public void setSslNotRequired(boolean sslNotRequired) {
+ this.sslNotRequired = sslNotRequired;
+ }
+
+ public Map getCredentials() {
+ return credentials;
+ }
+
+ public void setCredentials(Map credentials) {
+ this.credentials = credentials;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
index 4e4619f2e6..80819792b5 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -8,15 +8,20 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ApplicationRepresentation;
+import org.keycloak.representations.idm.ApplicationInstallationRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.ScopeMappingRepresentation;
import org.keycloak.representations.idm.UserRoleMappingRepresentation;
+import org.keycloak.services.resources.flows.Urls;
+import java.net.URI;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.Map;
+import java.util.HashMap;
/**
* @author Bill Burke
@@ -167,4 +172,25 @@ public class ApplicationManager {
return rep;
}
+
+ public ApplicationInstallationRepresentation toInstallationRepresentation(RealmModel realmModel, ApplicationModel applicationModel, URI baseUri) {
+ ApplicationInstallationRepresentation rep = new ApplicationInstallationRepresentation();
+ rep.setRealm(realmModel.getId());
+ rep.setRealmPublicKey(realmModel.getPublicKeyPem());
+ rep.setSslNotRequired(realmModel.isSslNotRequired());
+
+ rep.setAuthUrl(Urls.realmLoginPage(baseUri, realmModel.getId()).toString());
+ rep.setCodeUrl(Urls.realmCode(baseUri, realmModel.getId()).toString());
+
+ rep.setResource(applicationModel.getId());
+
+ Map creds = new HashMap();
+ creds.put(CredentialRepresentation.PASSWORD, "INSERT APPLICATION PASSWORD");
+ if (applicationModel.getApplicationUser().isTotp()) {
+ creds.put(CredentialRepresentation.TOTP, "INSERT APPLICATION TOTP");
+ }
+ rep.setCredentials(creds);
+
+ return rep;
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index c7945bd9b4..93682c4e0d 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -7,12 +7,10 @@ import org.keycloak.models.ModelProvider;
import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.services.managers.TokenManager;
-import javax.annotation.PreDestroy;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Set;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
index aa0671fb60..0c05fc9c17 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
@@ -1,11 +1,13 @@
package org.keycloak.services.resources.admin;
+import org.codehaus.jackson.map.ObjectMapper;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
+import org.keycloak.representations.idm.ApplicationInstallationRepresentation;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.ApplicationManager;
@@ -17,7 +19,10 @@ import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
import java.util.List;
import java.util.Set;
@@ -30,6 +35,8 @@ public class ApplicationResource extends RoleContainerResource {
protected RealmModel realm;
protected ApplicationModel application;
protected KeycloakSession session;
+ @Context
+ protected UriInfo uriInfo;
public ApplicationResource(RealmModel realm, ApplicationModel applicationModel, KeycloakSession session) {
super(applicationModel);
@@ -54,6 +61,20 @@ public class ApplicationResource extends RoleContainerResource {
return applicationManager.toRepresentation(application);
}
+
+ @GET
+ @NoCache
+ @Path("installation")
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getInstallation() throws IOException {
+ ApplicationManager applicationManager = new ApplicationManager(new RealmManager(session));
+ ApplicationInstallationRepresentation rep = applicationManager.toInstallationRepresentation(realm, application, uriInfo.getBaseUri());
+
+ // TODO Temporary solution to pretty-print
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rep);
+ }
+
@DELETE
@NoCache
public void deleteApplication() {
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index a2061aa6a0..169da3fed9 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -120,6 +120,10 @@ public class Urls {
return tokenBase(baseUri).path(TokenService.class, "registerPage").build(realmId);
}
+ public static URI realmCode(URI baseUri, String realmId) {
+ return tokenBase(baseUri).path(TokenService.class, "accessCodeToToken").build(realmId);
+ }
+
private static UriBuilder saasBase(URI baseUri) {
return UriBuilder.fromUri(baseUri).path(SaasService.class);
}