Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2013-11-26 15:40:24 -05:00
commit 3a9f9d73c2
17 changed files with 296 additions and 14 deletions

View file

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

View file

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

View file

@ -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 = "<div class=\"onoffswitch\">" +
"<input type=\"checkbox\" data-ng-model=\"ngModel\" class=\"onoffswitch-checkbox\" name=\"" + attrs.name + "\" id=\"" + attrs.id + "\">" +

View file

@ -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');

View file

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

View file

@ -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 = '<span class="primary">Are you sure you want to permanently delete the ' + type + ' "' + name + '"?</span>' +
var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
var msg = '<span class="primary">Are you sure you want to permanently delete the ' + escapeHtml(type) + ' "' + escapeHtml(name) + '"?</span>' +
'<span>This action can\'t be undone.</span>';
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',

View file

@ -6,7 +6,7 @@
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}">Settings</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/applications/{{application.id}}/credentials">Credentials</a></li>
<li><a href="#">Installation</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/installation">Installation</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
<li><a href="#">Sessions</a></li>

View file

@ -6,7 +6,7 @@
<ul class="rcue-tabs">
<li class="active"><a href="#/realms/{{realm.id}}/applications/{{application.id}}">Settings</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/credentials">Credentials</a></li>
<li><a href="#">Installation</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/installation">Installation</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
<li><a href="#">Sessions</a></li>

View file

@ -0,0 +1,27 @@
<div id="wrapper" class="container">
<div class="row">
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<div class="top-nav" data-ng-show="!create">
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}">Settings</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/credentials">Credentials</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/applications/{{application.id}}/installation">Installation</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
<li><a href="#">Sessions</a></li>
</ul>
</div>
<div class="top-nav" data-ng-show="create">
<ul class="rcue-tabs">
<li></li>
</ul>
</div>
<div id="content">
<a class="button primary" href="{{download}}" download="keycloak.json" type="submit">Download</a></br>
<textarea style="width: 100%;" rows="20" onclick="this.select()">{{installation | json}}</textarea>
</div>
</div>
<div id="container-right-bg"></div>
</div>
</div>

View file

@ -6,7 +6,7 @@
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}">Settings</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/credentials">Credentials</a></li>
<li><a href="#">Installation</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/installation">Installation</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/applications/{{application.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
<li><a href="#">Sessions</a></li>

View file

@ -6,7 +6,7 @@
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}">Settings</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/credentials">Credentials</a></li>
<li><a href="#">Installation</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/installation">Installation</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/applications/{{application.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
<li><a href="#">Sessions</a></li>

View file

@ -1,5 +1,14 @@
<div id="wrapper" class="container">
<div class="row">
Page not found
<div class="bs-sidebar col-md-3 clearfix"></div>
<div id="content-area" class="col-md-9" role="main">
<div class="error-container">
<h2>Page <strong>not found</strong>...</h2>
<p class="instruction">We could not find the page you are looking for. Please make sure the URL you entered is correct.</p>
<a href="#" class="link-right">Go to the home page &raquo;</a>
<!-- <a href="#" class="link-right">Go to the realm page &raquo;</a> -->
</div>
</div>
<div id="container-right-bg"></div>
</div>
</div>

View file

@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
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<String, String> 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<String, String> getCredentials() {
return credentials;
}
public void setCredentials(Map<String, String> credentials) {
this.credentials = credentials;
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -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<String, String> creds = new HashMap<String, String>();
creds.put(CredentialRepresentation.PASSWORD, "INSERT APPLICATION PASSWORD");
if (applicationModel.getApplicationUser().isTotp()) {
creds.put(CredentialRepresentation.TOTP, "INSERT APPLICATION TOTP");
}
rep.setCredentials(creds);
return rep;
}
}

View file

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

View file

@ -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() {

View file

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