From 45068cd299bc6268abb0da0dec07eb39bde5dfce Mon Sep 17 00:00:00 2001 From: vrockai Date: Tue, 26 Nov 2013 14:11:47 +0100 Subject: [PATCH 1/5] KEYCLOAK-170 escape HTML chars in Dialog service --- .../resources/META-INF/resources/admin/js/services.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 c0e3c6a9f4..7c209d035a 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', From 29c416662603b1ddb7820ba7a976248aa6ada594 Mon Sep 17 00:00:00 2001 From: Gabriel Cardoso Date: Tue, 26 Nov 2013 12:27:11 -0200 Subject: [PATCH 2/5] KEYCLOAK-162 --- .../resources/admin-ui/css/admin-console.css | 33 +++++++++++++++- .../resources/admin-ui/css/admin-console.less | 39 ++++++++++++++++++- .../resources/admin/partials/notfound.html | 11 +++++- 3 files changed, 80 insertions(+), 3 deletions(-) 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/partials/notfound.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/notfound.html index 12b2139832..76d74c4289 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 list of realms » + +
+
+
\ No newline at end of file From 69e27bcf87c2a8ae51397963a7c3e00fbc5e162e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 26 Nov 2013 14:37:51 +0000 Subject: [PATCH 3/5] Changed text on notfound page --- .../resources/META-INF/resources/admin/partials/notfound.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 76d74c4289..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 @@ -5,7 +5,7 @@

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 list of realms » + Go to the home page »
From d20be22ab5ce7df9c25d5d69f0fd30c7e0b101cb Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 26 Nov 2013 14:42:05 +0000 Subject: [PATCH 4/5] ON/OFF or YES/NO - that is the question! --- .../src/main/resources/META-INF/resources/admin/js/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3f571d3c60..311175d513 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 @@ -431,8 +431,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 = "
" + "" + From c6f4e21b845b2e369ced7e14059e256049b07ba4 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 26 Nov 2013 17:28:00 +0000 Subject: [PATCH 5/5] KEYCLOAK-153 Realm installation page --- .../META-INF/resources/admin/js/app.js | 13 ++- .../admin/js/controllers/applications.js | 6 ++ .../META-INF/resources/admin/js/loaders.js | 9 +++ .../META-INF/resources/admin/js/services.js | 16 ++++ .../partials/application-credentials.html | 2 +- .../admin/partials/application-detail.html | 2 +- .../partials/application-installation.html | 27 +++++++ .../partials/application-role-detail.html | 2 +- .../admin/partials/application-role-list.html | 2 +- ...ApplicationInstallationRepresentation.java | 80 +++++++++++++++++++ .../services/managers/ApplicationManager.java | 26 ++++++ .../resources/KeycloakApplication.java | 2 - .../resources/admin/ApplicationResource.java | 21 +++++ .../services/resources/flows/Urls.java | 4 + 14 files changed, 205 insertions(+), 7 deletions(-) create mode 100755 admin-ui/src/main/resources/META-INF/resources/admin/partials/application-installation.html create mode 100755 core/src/main/java/org/keycloak/representations/idm/ApplicationInstallationRepresentation.java 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 311175d513..3b1d433de2 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 : { 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 8846801296..790e53d53a 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 97607f4c3f..41d74a64d7 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 18aa471950..b66661749a 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 @@ -209,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 5cdaee909b..6d1ffa170d 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 @@ +
      +
      +
      +
      + +
      +
        +
      • +
      +
      +
      + Download
      + +
      +
      +
      +
      +
      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/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 6b97269212..51c5e53319 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 @@ -159,4 +164,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); }