keycloakify 8.4.1 → 9.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +11 -53
  2. package/account/kcContext/KcContext.js.map +1 -1
  3. package/account/kcContext/createGetKcContext.js +2 -6
  4. package/account/kcContext/createGetKcContext.js.map +1 -1
  5. package/account/kcContext/kcContextMocks.js +4 -3
  6. package/account/kcContext/kcContextMocks.js.map +1 -1
  7. package/bin/constants.d.ts +7 -0
  8. package/bin/constants.js +10 -0
  9. package/bin/constants.js.map +1 -0
  10. package/bin/copy-keycloak-resources-to-public.js +34 -22
  11. package/bin/copy-keycloak-resources-to-public.js.map +1 -1
  12. package/bin/download-builtin-keycloak-theme.d.ts +4 -1
  13. package/bin/download-builtin-keycloak-theme.js +8 -6
  14. package/bin/download-builtin-keycloak-theme.js.map +1 -1
  15. package/bin/eject-keycloak-page.js +5 -3
  16. package/bin/eject-keycloak-page.js.map +1 -1
  17. package/bin/getSrcDirPath.d.ts +1 -1
  18. package/bin/getSrcDirPath.js +4 -4
  19. package/bin/getSrcDirPath.js.map +1 -1
  20. package/bin/initialize-email-theme.js +9 -9
  21. package/bin/initialize-email-theme.js.map +1 -1
  22. package/bin/keycloakify/BuildOptions.d.ts +8 -6
  23. package/bin/keycloakify/BuildOptions.js +63 -71
  24. package/bin/keycloakify/BuildOptions.js.map +1 -1
  25. package/bin/keycloakify/generateFtl/generateFtl.d.ts +2 -3
  26. package/bin/keycloakify/generateFtl/generateFtl.js +3 -4
  27. package/bin/keycloakify/generateFtl/generateFtl.js.map +1 -1
  28. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/AccountPages.java +33 -0
  29. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/AccountProvider.java +76 -0
  30. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/AccountProviderFactory.java +25 -0
  31. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/AccountSpi.java +50 -0
  32. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/FreeMarkerAccountProvider.java +424 -0
  33. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/FreeMarkerAccountProviderFactory.java +51 -0
  34. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/Templates.java +51 -0
  35. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/AccountBean.java +91 -0
  36. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/AccountFederatedIdentityBean.java +157 -0
  37. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/ApplicationsBean.java +258 -0
  38. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/AuthorizationBean.java +515 -0
  39. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/FeaturesBean.java +56 -0
  40. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/LogBean.java +95 -0
  41. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/PasswordBean.java +34 -0
  42. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/RealmBean.java +75 -0
  43. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/ReferrerBean.java +38 -0
  44. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/SessionsBean.java +93 -0
  45. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/TotpBean.java +125 -0
  46. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/UrlBean.java +121 -0
  47. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/services/AccountUrls.java +115 -0
  48. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/services/resources/account/AccountFormService.java +1310 -0
  49. package/bin/keycloakify/generateJavaStackFiles/account-v1-java/services/resources/account/AccountFormServiceFactory.java +64 -0
  50. package/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.d.ts +7 -0
  51. package/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.js +194 -0
  52. package/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.js.map +1 -0
  53. package/bin/keycloakify/{generateJavaStackFiles.d.ts → generateJavaStackFiles/generateJavaStackFiles.d.ts} +6 -6
  54. package/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.js +276 -0
  55. package/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.js.map +1 -0
  56. package/bin/keycloakify/generateJavaStackFiles/index.d.ts +1 -0
  57. package/bin/keycloakify/generateJavaStackFiles/index.js +18 -0
  58. package/bin/keycloakify/generateJavaStackFiles/index.js.map +1 -0
  59. package/bin/keycloakify/generateStartKeycloakTestingContainer.d.ts +1 -3
  60. package/bin/keycloakify/generateStartKeycloakTestingContainer.js +15 -6
  61. package/bin/keycloakify/generateStartKeycloakTestingContainer.js.map +1 -1
  62. package/bin/keycloakify/generateTheme/downloadKeycloakStaticResources.d.ts +5 -2
  63. package/bin/keycloakify/generateTheme/downloadKeycloakStaticResources.js +11 -9
  64. package/bin/keycloakify/generateTheme/downloadKeycloakStaticResources.js.map +1 -1
  65. package/bin/keycloakify/generateTheme/generateMessageProperties.d.ts +1 -1
  66. package/bin/keycloakify/generateTheme/generateTheme.d.ts +6 -5
  67. package/bin/keycloakify/generateTheme/generateTheme.js +59 -49
  68. package/bin/keycloakify/generateTheme/generateTheme.js.map +1 -1
  69. package/bin/keycloakify/generateTheme/readExtraPageNames.d.ts +1 -1
  70. package/bin/keycloakify/generateTheme/readExtraPageNames.js.map +1 -1
  71. package/bin/keycloakify/generateTheme/readFieldNameUsage.d.ts +1 -1
  72. package/bin/keycloakify/generateTheme/readStaticResourcesUsage.d.ts +1 -1
  73. package/bin/keycloakify/keycloakify.js +50 -91
  74. package/bin/keycloakify/keycloakify.js.map +1 -1
  75. package/bin/keycloakify/parsedPackageJson.d.ts +29 -31
  76. package/bin/keycloakify/parsedPackageJson.js +7 -8
  77. package/bin/keycloakify/parsedPackageJson.js.map +1 -1
  78. package/bin/tools/downloadAndUnzip.d.ts +1 -1
  79. package/bin/tools/downloadAndUnzip.js +24 -26
  80. package/bin/tools/downloadAndUnzip.js.map +1 -1
  81. package/bin/tools/getAbsoluteAndInOsFormatPath.d.ts +4 -0
  82. package/bin/tools/getAbsoluteAndInOsFormatPath.js +15 -0
  83. package/bin/tools/getAbsoluteAndInOsFormatPath.js.map +1 -0
  84. package/bin/tools/pathJoin.js +1 -1
  85. package/bin/tools/pathJoin.js.map +1 -1
  86. package/login/kcContext/KcContext.js.map +1 -1
  87. package/login/kcContext/createGetKcContext.js +2 -6
  88. package/login/kcContext/createGetKcContext.js.map +1 -1
  89. package/login/kcContext/kcContextMocks.js +5 -4
  90. package/login/kcContext/kcContextMocks.js.map +1 -1
  91. package/package.json +92 -21
  92. package/src/account/kcContext/KcContext.ts +2 -1
  93. package/src/account/kcContext/createGetKcContext.ts +2 -7
  94. package/src/account/kcContext/kcContextMocks.ts +5 -3
  95. package/src/bin/constants.ts +9 -0
  96. package/src/bin/copy-keycloak-resources-to-public.ts +20 -19
  97. package/src/bin/download-builtin-keycloak-theme.ts +14 -6
  98. package/src/bin/eject-keycloak-page.ts +5 -9
  99. package/src/bin/getSrcDirPath.ts +4 -4
  100. package/src/bin/initialize-email-theme.ts +7 -7
  101. package/src/bin/keycloakify/BuildOptions.ts +78 -59
  102. package/src/bin/keycloakify/generateFtl/generateFtl.ts +4 -7
  103. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/AccountPages.java +33 -0
  104. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/AccountProvider.java +76 -0
  105. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/AccountProviderFactory.java +25 -0
  106. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/AccountSpi.java +50 -0
  107. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/FreeMarkerAccountProvider.java +424 -0
  108. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/FreeMarkerAccountProviderFactory.java +51 -0
  109. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/Templates.java +51 -0
  110. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/AccountBean.java +91 -0
  111. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/AccountFederatedIdentityBean.java +157 -0
  112. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/ApplicationsBean.java +258 -0
  113. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/AuthorizationBean.java +515 -0
  114. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/FeaturesBean.java +56 -0
  115. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/LogBean.java +95 -0
  116. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/PasswordBean.java +34 -0
  117. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/RealmBean.java +75 -0
  118. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/ReferrerBean.java +38 -0
  119. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/SessionsBean.java +93 -0
  120. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/TotpBean.java +125 -0
  121. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/forms/account/freemarker/model/UrlBean.java +121 -0
  122. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/services/AccountUrls.java +115 -0
  123. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/services/resources/account/AccountFormService.java +1310 -0
  124. package/src/bin/keycloakify/generateJavaStackFiles/account-v1-java/services/resources/account/AccountFormServiceFactory.java +64 -0
  125. package/src/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.ts +92 -0
  126. package/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts +211 -0
  127. package/src/bin/keycloakify/generateJavaStackFiles/index.ts +1 -0
  128. package/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts +21 -22
  129. package/src/bin/keycloakify/generateTheme/downloadKeycloakStaticResources.ts +19 -16
  130. package/src/bin/keycloakify/generateTheme/generateMessageProperties.ts +1 -1
  131. package/src/bin/keycloakify/generateTheme/generateTheme.ts +78 -72
  132. package/src/bin/keycloakify/generateTheme/readExtraPageNames.ts +2 -1
  133. package/src/bin/keycloakify/generateTheme/readFieldNameUsage.ts +1 -1
  134. package/src/bin/keycloakify/generateTheme/readStaticResourcesUsage.ts +1 -1
  135. package/src/bin/keycloakify/keycloakify.ts +22 -46
  136. package/src/bin/keycloakify/parsedPackageJson.ts +11 -13
  137. package/src/bin/tools/downloadAndUnzip.ts +6 -7
  138. package/src/bin/tools/getAbsoluteAndInOsFormatPath.ts +15 -0
  139. package/src/bin/tools/pathJoin.ts +1 -1
  140. package/src/login/kcContext/KcContext.ts +2 -1
  141. package/src/login/kcContext/createGetKcContext.ts +2 -7
  142. package/src/login/kcContext/kcContextMocks.ts +7 -5
  143. package/bin/keycloakify/generateJavaStackFiles.js +0 -103
  144. package/bin/keycloakify/generateJavaStackFiles.js.map +0 -1
  145. package/bin/mockTestingResourcesPath.d.ts +0 -3
  146. package/bin/mockTestingResourcesPath.js +0 -8
  147. package/bin/mockTestingResourcesPath.js.map +0 -1
  148. package/bin/tools/jar.d.ts +0 -33
  149. package/bin/tools/jar.js +0 -241
  150. package/bin/tools/jar.js.map +0 -1
  151. package/bin/tools/walk.d.ts +0 -8
  152. package/bin/tools/walk.js +0 -125
  153. package/bin/tools/walk.js.map +0 -1
  154. package/src/bin/keycloakify/generateJavaStackFiles.ts +0 -84
  155. package/src/bin/mockTestingResourcesPath.ts +0 -5
  156. package/src/bin/tools/jar.ts +0 -99
  157. package/src/bin/tools/walk.ts +0 -19
@@ -0,0 +1,1310 @@
1
+ /*
2
+ * Copyright 2022 Red Hat, Inc. and/or its affiliates
3
+ * and other contributors as indicated by the @author tags.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ package org.keycloak.services.resources.account;
18
+
19
+ import com.google.common.base.Strings;
20
+ import jakarta.ws.rs.Consumes;
21
+ import jakarta.ws.rs.FormParam;
22
+ import jakarta.ws.rs.GET;
23
+ import jakarta.ws.rs.NotFoundException;
24
+ import jakarta.ws.rs.POST;
25
+ import jakarta.ws.rs.Path;
26
+ import jakarta.ws.rs.PathParam;
27
+ import jakarta.ws.rs.Produces;
28
+ import jakarta.ws.rs.QueryParam;
29
+ import jakarta.ws.rs.core.MediaType;
30
+ import jakarta.ws.rs.core.MultivaluedMap;
31
+ import jakarta.ws.rs.core.Response;
32
+ import jakarta.ws.rs.core.Response.Status;
33
+ import jakarta.ws.rs.core.UriBuilder;
34
+ import jakarta.ws.rs.core.UriInfo;
35
+ import java.io.IOException;
36
+ import java.lang.reflect.Method;
37
+ import java.net.URI;
38
+ import java.nio.charset.StandardCharsets;
39
+ import java.security.MessageDigest;
40
+ import java.util.ArrayList;
41
+ import java.util.Arrays;
42
+ import java.util.EnumMap;
43
+ import java.util.HashSet;
44
+ import java.util.Iterator;
45
+ import java.util.List;
46
+ import java.util.Map;
47
+ import java.util.Objects;
48
+ import java.util.Set;
49
+ import java.util.UUID;
50
+ import java.util.stream.Collectors;
51
+ import lombok.extern.jbosslog.JBossLog;
52
+ import org.jboss.logging.Logger;
53
+ import org.keycloak.authorization.AuthorizationProvider;
54
+ import org.keycloak.authorization.model.PermissionTicket;
55
+ import org.keycloak.authorization.model.Policy;
56
+ import org.keycloak.authorization.model.Resource;
57
+ import org.keycloak.authorization.model.ResourceServer;
58
+ import org.keycloak.authorization.model.Scope;
59
+ import org.keycloak.authorization.store.PermissionTicketStore;
60
+ import org.keycloak.authorization.store.PolicyStore;
61
+ import org.keycloak.authorization.store.ScopeStore;
62
+ import org.keycloak.common.Profile;
63
+ import org.keycloak.common.util.Base64Url;
64
+ import org.keycloak.common.util.Time;
65
+ import org.keycloak.common.util.UriUtils;
66
+ import org.keycloak.events.Details;
67
+ import org.keycloak.events.Errors;
68
+ import org.keycloak.events.Event;
69
+ import org.keycloak.events.EventBuilder;
70
+ import org.keycloak.events.EventStoreProvider;
71
+ import org.keycloak.events.EventType;
72
+ import org.keycloak.forms.account.AccountPages;
73
+ import org.keycloak.forms.account.AccountProvider;
74
+ import org.keycloak.forms.login.LoginFormsProvider;
75
+ import org.keycloak.locale.LocaleSelectorProvider;
76
+ import org.keycloak.locale.LocaleUpdaterProvider;
77
+ import org.keycloak.models.AccountRoles;
78
+ import org.keycloak.models.AuthenticatedClientSessionModel;
79
+ import org.keycloak.models.ClientModel;
80
+ import org.keycloak.models.ClientSessionContext;
81
+ import org.keycloak.models.FederatedIdentityModel;
82
+ import org.keycloak.models.KeycloakSession;
83
+ import org.keycloak.models.ModelException;
84
+ import org.keycloak.models.OTPPolicy;
85
+ import org.keycloak.models.RealmModel;
86
+ import org.keycloak.models.UserCredentialModel;
87
+ import org.keycloak.models.UserModel;
88
+ import org.keycloak.models.UserSessionModel;
89
+ import org.keycloak.models.credential.OTPCredentialModel;
90
+ import org.keycloak.models.credential.PasswordCredentialModel;
91
+ import org.keycloak.models.utils.CredentialValidation;
92
+ import org.keycloak.models.utils.FormMessage;
93
+ import org.keycloak.protocol.oidc.TokenManager;
94
+ import org.keycloak.protocol.oidc.utils.RedirectUtils;
95
+ import org.keycloak.representations.IDToken;
96
+ import org.keycloak.services.AccountUrls;
97
+ import org.keycloak.services.ErrorResponse;
98
+ import org.keycloak.services.ForbiddenException;
99
+ import org.keycloak.services.ServicesLogger;
100
+ import org.keycloak.services.managers.AppAuthManager;
101
+ import org.keycloak.services.managers.Auth;
102
+ import org.keycloak.services.managers.AuthenticationManager;
103
+ import org.keycloak.services.managers.AuthenticationSessionManager;
104
+ import org.keycloak.services.managers.UserConsentManager;
105
+ import org.keycloak.services.messages.Messages;
106
+ import org.keycloak.services.resource.AccountResourceProvider;
107
+ import org.keycloak.services.resources.AbstractSecuredLocalService;
108
+ import org.keycloak.services.resources.RealmsResource;
109
+ import org.keycloak.services.util.DefaultClientSessionContext;
110
+ import org.keycloak.services.util.ResolveRelative;
111
+ import org.keycloak.services.validation.Validation;
112
+ import org.keycloak.sessions.AuthenticationSessionModel;
113
+ import org.keycloak.storage.ReadOnlyException;
114
+ import org.keycloak.theme.Theme;
115
+ import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
116
+ import org.keycloak.userprofile.UserProfile;
117
+ import org.keycloak.userprofile.UserProfileContext;
118
+ import org.keycloak.userprofile.UserProfileProvider;
119
+ import org.keycloak.userprofile.ValidationException;
120
+ import org.keycloak.util.JsonSerialization;
121
+ import org.keycloak.utils.CredentialHelper;
122
+
123
+ /**
124
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
125
+ */
126
+ @JBossLog
127
+ public class AccountFormService extends AbstractSecuredLocalService
128
+ implements AccountResourceProvider {
129
+
130
+ @Override
131
+ public Object getResource() {
132
+ return this;
133
+ }
134
+
135
+ @Override
136
+ public void close() {}
137
+
138
+ private static final Logger logger = Logger.getLogger(AccountFormService.class);
139
+
140
+ private static Set<String> VALID_PATHS = new HashSet<>();
141
+
142
+ static {
143
+ for (Method m : AccountFormService.class.getMethods()) {
144
+ Path p = m.getAnnotation(Path.class);
145
+ if (p != null) {
146
+ VALID_PATHS.add(p.value());
147
+ }
148
+ }
149
+ }
150
+
151
+ // Used when some other context (ie. IdentityBrokerService) wants to forward error to account
152
+ // management and display it here
153
+ public static final String ACCOUNT_MGMT_FORWARDED_ERROR_NOTE = "ACCOUNT_MGMT_FORWARDED_ERROR";
154
+
155
+ private final AppAuthManager authManager;
156
+ private final EventBuilder event;
157
+ private AccountProvider account;
158
+ private EventStoreProvider eventStore;
159
+
160
+ public AccountFormService(KeycloakSession session, ClientModel client, EventBuilder event) {
161
+ super(session, client);
162
+ this.event = event;
163
+ this.authManager = new AppAuthManager();
164
+ init();
165
+ }
166
+
167
+ public void init() {
168
+ log.info("init");
169
+ session.getContext().setClient(client);
170
+ eventStore = session.getProvider(EventStoreProvider.class);
171
+
172
+ account =
173
+ session
174
+ .getProvider(AccountProvider.class)
175
+ .setRealm(realm)
176
+ .setUriInfo(session.getContext().getUri())
177
+ .setHttpHeaders(headers);
178
+
179
+ AuthenticationManager.AuthResult authResult =
180
+ authManager.authenticateIdentityCookie(session, realm);
181
+ if (authResult != null) {
182
+ stateChecker = (String) session.getAttribute("state_checker");
183
+ auth =
184
+ new Auth(
185
+ realm,
186
+ authResult.getToken(),
187
+ authResult.getUser(),
188
+ client,
189
+ authResult.getSession(),
190
+ true);
191
+ account.setStateChecker(stateChecker);
192
+ }
193
+
194
+ String requestOrigin = UriUtils.getOrigin(session.getContext().getUri().getBaseUri());
195
+
196
+ String origin = headers.getRequestHeaders().getFirst("Origin");
197
+ if (origin != null && !origin.equals("null") && !requestOrigin.equals(origin)) {
198
+ throw new ForbiddenException();
199
+ }
200
+
201
+ if (!request.getHttpMethod().equals("GET")) {
202
+ String referrer = headers.getRequestHeaders().getFirst("Referer");
203
+ if (referrer != null && !requestOrigin.equals(UriUtils.getOrigin(referrer))) {
204
+ throw new ForbiddenException();
205
+ }
206
+ }
207
+
208
+ if (authResult != null) {
209
+ UserSessionModel userSession = authResult.getSession();
210
+ if (userSession != null) {
211
+ AuthenticatedClientSessionModel clientSession =
212
+ userSession.getAuthenticatedClientSessionByClient(client.getId());
213
+ if (clientSession == null) {
214
+ clientSession =
215
+ session.sessions().createClientSession(userSession.getRealm(), client, userSession);
216
+ }
217
+ auth.setClientSession(clientSession);
218
+ }
219
+
220
+ account.setUser(auth.getUser());
221
+
222
+ ClientSessionContext clientSessionCtx =
223
+ DefaultClientSessionContext.fromClientSessionScopeParameter(
224
+ auth.getClientSession(), session);
225
+ IDToken idToken =
226
+ new TokenManager()
227
+ .responseBuilder(realm, client, event, session, userSession, clientSessionCtx)
228
+ .accessToken(authResult.getToken())
229
+ .generateIDToken()
230
+ .getIdToken();
231
+ idToken.issuedFor(client.getClientId());
232
+ account.setIdTokenHint(session.tokens().encodeAndEncrypt(idToken));
233
+ }
234
+
235
+ account.setFeatures(
236
+ realm.isIdentityFederationEnabled(),
237
+ eventStore != null && realm.isEventsEnabled(),
238
+ true,
239
+ Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION));
240
+ }
241
+
242
+ public static UriBuilder accountServiceBaseUrl(UriInfo uriInfo) {
243
+ UriBuilder base =
244
+ uriInfo
245
+ .getBaseUriBuilder()
246
+ .path(RealmsResource.class)
247
+ .path(RealmsResource.class, "getAccountService");
248
+ return base;
249
+ }
250
+
251
+ public static UriBuilder accountServiceApplicationPage(UriInfo uriInfo) {
252
+ return accountServiceBaseUrl(uriInfo).path(AccountFormService.class, "applicationsPage");
253
+ }
254
+
255
+ protected Set<String> getValidPaths() {
256
+ return AccountFormService.VALID_PATHS;
257
+ }
258
+
259
+ private Response forwardToPage(String path, AccountPages page) {
260
+ if (auth != null) {
261
+ try {
262
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
263
+ } catch (ForbiddenException e) {
264
+ return session
265
+ .getProvider(LoginFormsProvider.class)
266
+ .setError(Messages.NO_ACCESS)
267
+ .createErrorPage(Response.Status.FORBIDDEN);
268
+ }
269
+
270
+ setReferrerOnPage();
271
+
272
+ UserSessionModel userSession = auth.getSession();
273
+
274
+ String tabId =
275
+ session
276
+ .getContext()
277
+ .getUri()
278
+ .getQueryParameters()
279
+ .getFirst(org.keycloak.models.Constants.TAB_ID);
280
+ if (tabId != null) {
281
+ AuthenticationSessionModel authSession =
282
+ new AuthenticationSessionManager(session)
283
+ .getAuthenticationSessionByIdAndClient(realm, userSession.getId(), client, tabId);
284
+ if (authSession != null) {
285
+ String forwardedError = authSession.getAuthNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
286
+ if (forwardedError != null) {
287
+ try {
288
+ FormMessage errorMessage =
289
+ JsonSerialization.readValue(forwardedError, FormMessage.class);
290
+ account.setError(
291
+ Response.Status.INTERNAL_SERVER_ERROR,
292
+ errorMessage.getMessage(),
293
+ errorMessage.getParameters());
294
+ authSession.removeAuthNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
295
+ } catch (IOException ioe) {
296
+ throw new RuntimeException(ioe);
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ String locale =
303
+ session
304
+ .getContext()
305
+ .getUri()
306
+ .getQueryParameters()
307
+ .getFirst(LocaleSelectorProvider.KC_LOCALE_PARAM);
308
+ if (locale != null) {
309
+ LocaleUpdaterProvider updater = session.getProvider(LocaleUpdaterProvider.class);
310
+ updater.updateUsersLocale(auth.getUser(), locale);
311
+ }
312
+
313
+ return account.createResponse(page);
314
+ } else {
315
+ return login(path);
316
+ }
317
+ }
318
+
319
+ private void setReferrerOnPage() {
320
+ String[] referrer = getReferrer();
321
+ if (referrer != null) {
322
+ account.setReferrer(referrer);
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Get account information.
328
+ *
329
+ * @return
330
+ */
331
+ @Path("/")
332
+ @GET
333
+ @Produces(MediaType.TEXT_HTML)
334
+ public Response accountPage() {
335
+ log.info("accountPage");
336
+ return forwardToPage(null, AccountPages.ACCOUNT);
337
+ }
338
+
339
+ public static UriBuilder totpUrl(UriBuilder base) {
340
+ return RealmsResource.accountUrl(base).path(AccountFormService.class, "totpPage");
341
+ }
342
+
343
+ @Path("totp")
344
+ @GET
345
+ public Response totpPage() {
346
+ account.setAttribute(
347
+ "mode", session.getContext().getUri().getQueryParameters().getFirst("mode"));
348
+ return forwardToPage("totp", AccountPages.TOTP);
349
+ }
350
+
351
+ public static UriBuilder passwordUrl(UriBuilder base) {
352
+ return RealmsResource.accountUrl(base).path(AccountFormService.class, "passwordPage");
353
+ }
354
+
355
+ @Path("password")
356
+ @GET
357
+ public Response passwordPage() {
358
+ if (auth != null) {
359
+ account.setPasswordSet(isPasswordSet(session, realm, auth.getUser()));
360
+ }
361
+
362
+ return forwardToPage("password", AccountPages.PASSWORD);
363
+ }
364
+
365
+ @Path("identity")
366
+ @GET
367
+ public Response federatedIdentityPage() {
368
+ return forwardToPage("identity", AccountPages.FEDERATED_IDENTITY);
369
+ }
370
+
371
+ @Path("log")
372
+ @GET
373
+ public Response logPage() {
374
+ if (!realm.isEventsEnabled()) {
375
+ throw new NotFoundException();
376
+ }
377
+
378
+ if (auth != null) {
379
+ List<Event> events =
380
+ eventStore
381
+ .createQuery()
382
+ .type(Constants.EXPOSED_LOG_EVENTS)
383
+ .realm(auth.getRealm().getId())
384
+ .user(auth.getUser().getId())
385
+ .maxResults(30)
386
+ .getResultStream()
387
+ .peek(
388
+ e -> {
389
+ if (e.getDetails() != null) {
390
+ Iterator<Map.Entry<String, String>> itr =
391
+ e.getDetails().entrySet().iterator();
392
+ while (itr.hasNext()) {
393
+ if (!Constants.EXPOSED_LOG_DETAILS.contains(itr.next().getKey())) {
394
+ itr.remove();
395
+ }
396
+ }
397
+ }
398
+ })
399
+ .collect(Collectors.toList());
400
+ account.setEvents(events);
401
+ }
402
+ return forwardToPage("log", AccountPages.LOG);
403
+ }
404
+
405
+ @Path("sessions")
406
+ @GET
407
+ public Response sessionsPage() {
408
+ if (auth != null) {
409
+ account.setSessions(
410
+ session
411
+ .sessions()
412
+ .getUserSessionsStream(realm, auth.getUser())
413
+ .collect(Collectors.toList()));
414
+ }
415
+ return forwardToPage("sessions", AccountPages.SESSIONS);
416
+ }
417
+
418
+ @Path("applications")
419
+ @GET
420
+ public Response applicationsPage() {
421
+ return forwardToPage("applications", AccountPages.APPLICATIONS);
422
+ }
423
+
424
+ /**
425
+ * Update account information.
426
+ *
427
+ * <p>Form params:
428
+ *
429
+ * <p>firstName lastName email
430
+ *
431
+ * @return
432
+ */
433
+ @Path("/")
434
+ @POST
435
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
436
+ public Response processAccountUpdate() {
437
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
438
+
439
+ if (auth == null) {
440
+ return login(null);
441
+ }
442
+
443
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
444
+
445
+ String action = formData.getFirst("submitAction");
446
+ if (action != null && action.equals("Cancel")) {
447
+ setReferrerOnPage();
448
+ return account.createResponse(AccountPages.ACCOUNT);
449
+ }
450
+
451
+ csrfCheck(formData);
452
+
453
+ UserModel user = auth.getUser();
454
+
455
+ event
456
+ .event(EventType.UPDATE_PROFILE)
457
+ .client(auth.getClient())
458
+ .user(auth.getUser())
459
+ .detail(Details.CONTEXT, "ACCOUNT_OLD");
460
+
461
+ UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
462
+ UserProfile profile = profileProvider.create(UserProfileContext.ACCOUNT, formData, user);
463
+
464
+ try {
465
+ // backward compatibility with old account console where attributes are not removed if missing
466
+ profile.update(false, new EventAuditingAttributeChangeListener(profile, event));
467
+ } catch (ValidationException pve) {
468
+ List<FormMessage> errors = Validation.getFormErrorsFromValidation(pve.getErrors());
469
+
470
+ if (!errors.isEmpty()) {
471
+ setReferrerOnPage();
472
+ Response.Status status = Status.OK;
473
+
474
+ if (pve.hasError(Messages.READ_ONLY_USERNAME)) {
475
+ status = Response.Status.BAD_REQUEST;
476
+ } else if (pve.hasError(Messages.EMAIL_EXISTS, Messages.USERNAME_EXISTS)) {
477
+ status = Response.Status.CONFLICT;
478
+ }
479
+
480
+ return account
481
+ .setErrors(status, errors)
482
+ .setProfileFormData(formData)
483
+ .createResponse(AccountPages.ACCOUNT);
484
+ }
485
+ } catch (ReadOnlyException e) {
486
+ setReferrerOnPage();
487
+ return account
488
+ .setError(Response.Status.BAD_REQUEST, Messages.READ_ONLY_USER)
489
+ .setProfileFormData(formData)
490
+ .createResponse(AccountPages.ACCOUNT);
491
+ }
492
+
493
+ event.success();
494
+ setReferrerOnPage();
495
+ return account.setSuccess(Messages.ACCOUNT_UPDATED).createResponse(AccountPages.ACCOUNT);
496
+ }
497
+
498
+ @Path("sessions")
499
+ @POST
500
+ public Response processSessionsLogout() {
501
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
502
+
503
+ if (auth == null) {
504
+ return login("sessions");
505
+ }
506
+
507
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
508
+ csrfCheck(formData);
509
+
510
+ UserModel user = auth.getUser();
511
+
512
+ // Rather decrease time a bit. To avoid situation when user is immediatelly redirected to login
513
+ // screen, then automatically authenticated (eg. with Kerberos) and then seeing issues due the
514
+ // stale token
515
+ // as time on the token will be same like notBefore
516
+ session.users().setNotBeforeForUser(realm, user, Time.currentTime() - 1);
517
+
518
+ session
519
+ .sessions()
520
+ .getUserSessionsStream(realm, user)
521
+ .collect(
522
+ Collectors
523
+ .toList()) // collect to avoid concurrent modification as backchannelLogout removes
524
+ // the user sessions.
525
+ .forEach(
526
+ userSession ->
527
+ AuthenticationManager.backchannelLogout(
528
+ session,
529
+ realm,
530
+ userSession,
531
+ session.getContext().getUri(),
532
+ clientConnection,
533
+ headers,
534
+ true));
535
+
536
+ UriBuilder builder =
537
+ AccountUrls.accountBase(session.getContext().getUri().getBaseUri())
538
+ .path(AccountFormService.class, "sessionsPage");
539
+ String referrer = session.getContext().getUri().getQueryParameters().getFirst("referrer");
540
+ if (referrer != null) {
541
+ builder.queryParam("referrer", referrer);
542
+ }
543
+ URI location = builder.build(realm.getName());
544
+ return Response.seeOther(location).build();
545
+ }
546
+
547
+ @Path("applications")
548
+ @POST
549
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
550
+ public Response processRevokeGrant() {
551
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
552
+
553
+ if (auth == null) {
554
+ return login("applications");
555
+ }
556
+
557
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
558
+ csrfCheck(formData);
559
+
560
+ String clientId = formData.getFirst("clientId");
561
+ if (clientId == null) {
562
+ setReferrerOnPage();
563
+ return account
564
+ .setError(Response.Status.BAD_REQUEST, Messages.CLIENT_NOT_FOUND)
565
+ .createResponse(AccountPages.APPLICATIONS);
566
+ }
567
+ ClientModel client = realm.getClientById(clientId);
568
+ if (client == null) {
569
+ setReferrerOnPage();
570
+ return account
571
+ .setError(Response.Status.BAD_REQUEST, Messages.CLIENT_NOT_FOUND)
572
+ .createResponse(AccountPages.APPLICATIONS);
573
+ }
574
+
575
+ // Revoke grant in UserModel
576
+ UserModel user = auth.getUser();
577
+ UserConsentManager.revokeConsentToClient(session, client, user);
578
+
579
+ event
580
+ .event(EventType.REVOKE_GRANT)
581
+ .client(auth.getClient())
582
+ .user(auth.getUser())
583
+ .detail(Details.REVOKED_CLIENT, client.getClientId())
584
+ .success();
585
+ setReferrerOnPage();
586
+
587
+ UriBuilder builder =
588
+ AccountUrls.accountBase(session.getContext().getUri().getBaseUri())
589
+ .path(AccountFormService.class, "applicationsPage");
590
+ String referrer = session.getContext().getUri().getQueryParameters().getFirst("referrer");
591
+ if (referrer != null) {
592
+ builder.queryParam("referrer", referrer);
593
+ }
594
+ URI location = builder.build(realm.getName());
595
+ return Response.seeOther(location).build();
596
+ }
597
+
598
+ /**
599
+ * Update the TOTP for this account.
600
+ *
601
+ * <p>form parameters:
602
+ *
603
+ * <p>totp - otp generated by authenticator totpSecret - totp secret to register
604
+ *
605
+ * @return
606
+ */
607
+ @Path("totp")
608
+ @POST
609
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
610
+ public Response processTotpUpdate() {
611
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
612
+
613
+ if (auth == null) {
614
+ return login("totp");
615
+ }
616
+
617
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
618
+
619
+ account.setAttribute(
620
+ "mode", session.getContext().getUri().getQueryParameters().getFirst("mode"));
621
+
622
+ String action = formData.getFirst("submitAction");
623
+ if (action != null && action.equals("Cancel")) {
624
+ setReferrerOnPage();
625
+ return account.createResponse(AccountPages.TOTP);
626
+ }
627
+
628
+ csrfCheck(formData);
629
+
630
+ UserModel user = auth.getUser();
631
+
632
+ if (action != null && action.equals("Delete")) {
633
+ String credentialId = formData.getFirst("credentialId");
634
+ if (credentialId == null) {
635
+ setReferrerOnPage();
636
+ return account
637
+ .setError(Status.OK, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST)
638
+ .createResponse(AccountPages.TOTP);
639
+ }
640
+ CredentialHelper.deleteOTPCredential(session, realm, user, credentialId);
641
+ event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
642
+ setReferrerOnPage();
643
+ return account.setSuccess(Messages.SUCCESS_TOTP_REMOVED).createResponse(AccountPages.TOTP);
644
+ } else {
645
+ String challengeResponse = formData.getFirst("totp");
646
+ String totpSecret = formData.getFirst("totpSecret");
647
+ String userLabel = formData.getFirst("userLabel");
648
+
649
+ OTPPolicy policy = realm.getOTPPolicy();
650
+ OTPCredentialModel credentialModel =
651
+ OTPCredentialModel.createFromPolicy(realm, totpSecret, userLabel);
652
+ if (Validation.isBlank(challengeResponse)) {
653
+ setReferrerOnPage();
654
+ return account.setError(Status.OK, Messages.MISSING_TOTP).createResponse(AccountPages.TOTP);
655
+ } else if (!CredentialValidation.validOTP(
656
+ challengeResponse, credentialModel, policy.getLookAheadWindow())) {
657
+ setReferrerOnPage();
658
+ return account.setError(Status.OK, Messages.INVALID_TOTP).createResponse(AccountPages.TOTP);
659
+ }
660
+
661
+ if (!CredentialHelper.createOTPCredential(
662
+ session, realm, user, challengeResponse, credentialModel)) {
663
+ setReferrerOnPage();
664
+ return account.setError(Status.OK, Messages.INVALID_TOTP).createResponse(AccountPages.TOTP);
665
+ }
666
+ event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
667
+
668
+ setReferrerOnPage();
669
+ return account.setSuccess(Messages.SUCCESS_TOTP).createResponse(AccountPages.TOTP);
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Update account password
675
+ *
676
+ * <p>Form params:
677
+ *
678
+ * <p>password - old password password-new pasword-confirm
679
+ *
680
+ * @return
681
+ */
682
+ @Path("password")
683
+ @POST
684
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
685
+ public Response processPasswordUpdate() {
686
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
687
+
688
+ if (auth == null) {
689
+ return login("password");
690
+ }
691
+
692
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
693
+
694
+ csrfCheck(formData);
695
+ UserModel user = auth.getUser();
696
+
697
+ boolean requireCurrent = isPasswordSet(session, realm, user);
698
+ account.setPasswordSet(requireCurrent);
699
+
700
+ String password = formData.getFirst("password");
701
+ String passwordNew = formData.getFirst("password-new");
702
+ String passwordConfirm = formData.getFirst("password-confirm");
703
+
704
+ EventBuilder errorEvent =
705
+ event
706
+ .clone()
707
+ .event(EventType.UPDATE_PASSWORD_ERROR)
708
+ .client(auth.getClient())
709
+ .user(auth.getSession().getUser());
710
+
711
+ if (requireCurrent) {
712
+ if (Validation.isBlank(password)) {
713
+ setReferrerOnPage();
714
+ errorEvent.error(Errors.PASSWORD_MISSING);
715
+ return account
716
+ .setError(Status.OK, Messages.MISSING_PASSWORD)
717
+ .createResponse(AccountPages.PASSWORD);
718
+ }
719
+
720
+ UserCredentialModel cred = UserCredentialModel.password(password);
721
+ if (!user.credentialManager().isValid(cred)) {
722
+ setReferrerOnPage();
723
+ errorEvent.error(Errors.INVALID_USER_CREDENTIALS);
724
+ return account
725
+ .setError(Status.OK, Messages.INVALID_PASSWORD_EXISTING)
726
+ .createResponse(AccountPages.PASSWORD);
727
+ }
728
+ }
729
+
730
+ if (Validation.isBlank(passwordNew)) {
731
+ setReferrerOnPage();
732
+ errorEvent.error(Errors.PASSWORD_MISSING);
733
+ return account
734
+ .setError(Status.OK, Messages.MISSING_PASSWORD)
735
+ .createResponse(AccountPages.PASSWORD);
736
+ }
737
+
738
+ if (!passwordNew.equals(passwordConfirm)) {
739
+ setReferrerOnPage();
740
+ errorEvent.error(Errors.PASSWORD_CONFIRM_ERROR);
741
+ return account
742
+ .setError(Status.OK, Messages.INVALID_PASSWORD_CONFIRM)
743
+ .createResponse(AccountPages.PASSWORD);
744
+ }
745
+
746
+ try {
747
+ user.credentialManager().updateCredential(UserCredentialModel.password(passwordNew, false));
748
+ } catch (ReadOnlyException mre) {
749
+ setReferrerOnPage();
750
+ errorEvent.error(Errors.NOT_ALLOWED);
751
+ return account
752
+ .setError(Response.Status.BAD_REQUEST, Messages.READ_ONLY_PASSWORD)
753
+ .createResponse(AccountPages.PASSWORD);
754
+ } catch (ModelException me) {
755
+ ServicesLogger.LOGGER.failedToUpdatePassword(me);
756
+ setReferrerOnPage();
757
+ errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
758
+ return account
759
+ .setError(Response.Status.NOT_ACCEPTABLE, me.getMessage(), me.getParameters())
760
+ .createResponse(AccountPages.PASSWORD);
761
+ } catch (Exception ape) {
762
+ ServicesLogger.LOGGER.failedToUpdatePassword(ape);
763
+ setReferrerOnPage();
764
+ errorEvent.detail(Details.REASON, ape.getMessage()).error(Errors.PASSWORD_REJECTED);
765
+ return account
766
+ .setError(Response.Status.INTERNAL_SERVER_ERROR, ape.getMessage())
767
+ .createResponse(AccountPages.PASSWORD);
768
+ }
769
+
770
+ session
771
+ .sessions()
772
+ .getUserSessionsStream(realm, user)
773
+ .filter(s -> !Objects.equals(s.getId(), auth.getSession().getId()))
774
+ .collect(
775
+ Collectors
776
+ .toList()) // collect to avoid concurrent modification as backchannelLogout removes
777
+ // the user sessions.
778
+ .forEach(
779
+ s ->
780
+ AuthenticationManager.backchannelLogout(
781
+ session,
782
+ realm,
783
+ s,
784
+ session.getContext().getUri(),
785
+ clientConnection,
786
+ headers,
787
+ true));
788
+
789
+ event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
790
+
791
+ setReferrerOnPage();
792
+ return account
793
+ .setPasswordSet(true)
794
+ .setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED)
795
+ .createResponse(AccountPages.PASSWORD);
796
+ }
797
+
798
+ @Path("identity")
799
+ @POST
800
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
801
+ public Response processFederatedIdentityUpdate() {
802
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
803
+
804
+ if (auth == null) {
805
+ return login("identity");
806
+ }
807
+
808
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
809
+ csrfCheck(formData);
810
+ UserModel user = auth.getUser();
811
+
812
+ String action = formData.getFirst("action");
813
+ String providerId = formData.getFirst("providerId");
814
+
815
+ if (Validation.isEmpty(providerId)) {
816
+ setReferrerOnPage();
817
+ return account
818
+ .setError(Status.OK, Messages.MISSING_IDENTITY_PROVIDER)
819
+ .createResponse(AccountPages.FEDERATED_IDENTITY);
820
+ }
821
+ AccountSocialAction accountSocialAction = AccountSocialAction.getAction(action);
822
+ if (accountSocialAction == null) {
823
+ setReferrerOnPage();
824
+ return account
825
+ .setError(Status.OK, Messages.INVALID_FEDERATED_IDENTITY_ACTION)
826
+ .createResponse(AccountPages.FEDERATED_IDENTITY);
827
+ }
828
+
829
+ if (!realm
830
+ .getIdentityProvidersStream()
831
+ .anyMatch(model -> Objects.equals(model.getAlias(), providerId))) {
832
+ setReferrerOnPage();
833
+ return account
834
+ .setError(Status.OK, Messages.IDENTITY_PROVIDER_NOT_FOUND)
835
+ .createResponse(AccountPages.FEDERATED_IDENTITY);
836
+ }
837
+
838
+ if (!user.isEnabled()) {
839
+ setReferrerOnPage();
840
+ return account
841
+ .setError(Status.OK, Messages.ACCOUNT_DISABLED)
842
+ .createResponse(AccountPages.FEDERATED_IDENTITY);
843
+ }
844
+
845
+ switch (accountSocialAction) {
846
+ case ADD:
847
+ String redirectUri =
848
+ UriBuilder.fromUri(
849
+ AccountUrls.accountFederatedIdentityPage(
850
+ session.getContext().getUri().getBaseUri(), realm.getName()))
851
+ .build()
852
+ .toString();
853
+
854
+ try {
855
+ String nonce = UUID.randomUUID().toString();
856
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
857
+ String input = nonce + auth.getSession().getId() + client.getClientId() + providerId;
858
+ byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
859
+ String hash = Base64Url.encode(check);
860
+ URI linkUrl =
861
+ AccountUrls.identityProviderLinkRequest(
862
+ this.session.getContext().getUri().getBaseUri(), providerId, realm.getName());
863
+ linkUrl =
864
+ UriBuilder.fromUri(linkUrl)
865
+ .queryParam("nonce", nonce)
866
+ .queryParam("hash", hash)
867
+ .queryParam("client_id", client.getClientId())
868
+ .queryParam("redirect_uri", redirectUri)
869
+ .build();
870
+ return Response.seeOther(linkUrl).build();
871
+ } catch (Exception spe) {
872
+ setReferrerOnPage();
873
+ return account
874
+ .setError(
875
+ Response.Status.INTERNAL_SERVER_ERROR, Messages.IDENTITY_PROVIDER_REDIRECT_ERROR)
876
+ .createResponse(AccountPages.FEDERATED_IDENTITY);
877
+ }
878
+ case REMOVE:
879
+ FederatedIdentityModel link = session.users().getFederatedIdentity(realm, user, providerId);
880
+ if (link != null) {
881
+
882
+ // Removing last social provider is not possible if you don't have other possibility to
883
+ // authenticate
884
+ if (session.users().getFederatedIdentitiesStream(realm, user).count() > 1
885
+ || user.getFederationLink() != null
886
+ || isPasswordSet(session, realm, user)) {
887
+ session.users().removeFederatedIdentity(realm, user, providerId);
888
+
889
+ logger.debugv(
890
+ "Social provider {0} removed successfully from user {1}",
891
+ providerId, user.getUsername());
892
+
893
+ event
894
+ .event(EventType.REMOVE_FEDERATED_IDENTITY)
895
+ .client(auth.getClient())
896
+ .user(auth.getUser())
897
+ .detail(Details.USERNAME, auth.getUser().getUsername())
898
+ .detail(Details.IDENTITY_PROVIDER, link.getIdentityProvider())
899
+ .detail(Details.IDENTITY_PROVIDER_USERNAME, link.getUserName())
900
+ .success();
901
+
902
+ setReferrerOnPage();
903
+ return account
904
+ .setSuccess(Messages.IDENTITY_PROVIDER_REMOVED)
905
+ .createResponse(AccountPages.FEDERATED_IDENTITY);
906
+ } else {
907
+ setReferrerOnPage();
908
+ return account
909
+ .setError(Status.OK, Messages.FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER)
910
+ .createResponse(AccountPages.FEDERATED_IDENTITY);
911
+ }
912
+ } else {
913
+ setReferrerOnPage();
914
+ return account
915
+ .setError(Status.OK, Messages.FEDERATED_IDENTITY_NOT_ACTIVE)
916
+ .createResponse(AccountPages.FEDERATED_IDENTITY);
917
+ }
918
+ default:
919
+ throw new IllegalArgumentException();
920
+ }
921
+ }
922
+
923
+ @Path("resource")
924
+ @GET
925
+ public Response resourcesPage(@QueryParam("resource_id") String resourceId) {
926
+ return forwardToPage("resource", AccountPages.RESOURCES);
927
+ }
928
+
929
+ @Path("resource/{resource_id}")
930
+ @GET
931
+ public Response resourceDetailPage(@PathParam("resource_id") String resourceId) {
932
+ return forwardToPage("resource", AccountPages.RESOURCE_DETAIL);
933
+ }
934
+
935
+ @Path("resource/{resource_id}/grant")
936
+ @GET
937
+ public Response resourceDetailPageAfterGrant(@PathParam("resource_id") String resourceId) {
938
+ return resourceDetailPage(resourceId);
939
+ }
940
+
941
+ @Path("resource/{resource_id}/grant")
942
+ @POST
943
+ public Response grantPermission(
944
+ @PathParam("resource_id") String resourceId,
945
+ @FormParam("action") String action,
946
+ @FormParam("permission_id") String[] permissionId,
947
+ @FormParam("requester") String requester) {
948
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
949
+
950
+ if (auth == null) {
951
+ return login("resource");
952
+ }
953
+
954
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
955
+
956
+ csrfCheck(formData);
957
+
958
+ AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class);
959
+ PermissionTicketStore ticketStore = authorization.getStoreFactory().getPermissionTicketStore();
960
+ Resource resource =
961
+ authorization.getStoreFactory().getResourceStore().findById(realm, null, resourceId);
962
+
963
+ if (resource == null) {
964
+ throw ErrorResponse.error("Invalid resource", Response.Status.BAD_REQUEST);
965
+ }
966
+
967
+ if (action == null) {
968
+ throw ErrorResponse.error("Invalid action", Response.Status.BAD_REQUEST);
969
+ }
970
+
971
+ boolean isGrant = "grant".equals(action);
972
+ boolean isDeny = "deny".equals(action);
973
+ boolean isRevoke = "revoke".equals(action);
974
+ boolean isRevokePolicy = "revokePolicy".equals(action);
975
+ boolean isRevokePolicyAll = "revokePolicyAll".equals(action);
976
+
977
+ if (isRevokePolicy || isRevokePolicyAll) {
978
+ List<String> ids = new ArrayList<>(Arrays.asList(permissionId));
979
+ Iterator<String> iterator = ids.iterator();
980
+ PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore();
981
+ ResourceServer resourceServer =
982
+ authorization.getStoreFactory().getResourceServerStore().findByClient(client);
983
+ Policy policy = null;
984
+
985
+ while (iterator.hasNext()) {
986
+ String id = iterator.next();
987
+
988
+ if (!id.contains(":")) {
989
+ policy = policyStore.findById(realm, resourceServer, id);
990
+ iterator.remove();
991
+ break;
992
+ }
993
+ }
994
+
995
+ Set<Scope> scopesToKeep = new HashSet<>();
996
+
997
+ if (isRevokePolicyAll) {
998
+ for (Scope scope : policy.getScopes()) {
999
+ policy.removeScope(scope);
1000
+ }
1001
+ } else {
1002
+ for (String id : ids) {
1003
+ scopesToKeep.add(
1004
+ authorization
1005
+ .getStoreFactory()
1006
+ .getScopeStore()
1007
+ .findById(realm, resourceServer, id.split(":")[1]));
1008
+ }
1009
+
1010
+ for (Scope scope : policy.getScopes()) {
1011
+ if (!scopesToKeep.contains(scope)) {
1012
+ policy.removeScope(scope);
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
+ if (policy.getScopes().isEmpty()) {
1018
+ for (Policy associated : policy.getAssociatedPolicies()) {
1019
+ policyStore.delete(realm, associated.getId());
1020
+ }
1021
+
1022
+ policyStore.delete(realm, policy.getId());
1023
+ }
1024
+ } else {
1025
+ Map<PermissionTicket.FilterOption, String> filters =
1026
+ new EnumMap<>(PermissionTicket.FilterOption.class);
1027
+
1028
+ filters.put(PermissionTicket.FilterOption.RESOURCE_ID, resource.getId());
1029
+ filters.put(
1030
+ PermissionTicket.FilterOption.REQUESTER,
1031
+ session.users().getUserByUsername(realm, requester).getId());
1032
+
1033
+ if (isRevoke) {
1034
+ filters.put(PermissionTicket.FilterOption.GRANTED, Boolean.TRUE.toString());
1035
+ } else {
1036
+ filters.put(PermissionTicket.FilterOption.GRANTED, Boolean.FALSE.toString());
1037
+ }
1038
+
1039
+ List<PermissionTicket> tickets =
1040
+ ticketStore.find(realm, resource.getResourceServer(), filters, null, null);
1041
+ Iterator<PermissionTicket> iterator = tickets.iterator();
1042
+
1043
+ while (iterator.hasNext()) {
1044
+ PermissionTicket ticket = iterator.next();
1045
+
1046
+ if (isGrant) {
1047
+ if (permissionId != null
1048
+ && permissionId.length > 0
1049
+ && !Arrays.asList(permissionId).contains(ticket.getId())) {
1050
+ continue;
1051
+ }
1052
+ }
1053
+
1054
+ if (isGrant && !ticket.isGranted()) {
1055
+ ticket.setGrantedTimestamp(System.currentTimeMillis());
1056
+ iterator.remove();
1057
+ } else if (isDeny || isRevoke) {
1058
+ if (permissionId != null
1059
+ && permissionId.length > 0
1060
+ && Arrays.asList(permissionId).contains(ticket.getId())) {
1061
+ iterator.remove();
1062
+ }
1063
+ }
1064
+ }
1065
+
1066
+ for (PermissionTicket ticket : tickets) {
1067
+ ticketStore.delete(client.getRealm(), ticket.getId());
1068
+ }
1069
+ }
1070
+
1071
+ if (isRevoke || isRevokePolicy || isRevokePolicyAll) {
1072
+ return forwardToPage("resource", AccountPages.RESOURCE_DETAIL);
1073
+ }
1074
+
1075
+ return forwardToPage("resource", AccountPages.RESOURCES);
1076
+ }
1077
+
1078
+ @Path("resource/{resource_id}/share")
1079
+ @GET
1080
+ public Response resourceDetailPageAfterShare(@PathParam("resource_id") String resourceId) {
1081
+ return resourceDetailPage(resourceId);
1082
+ }
1083
+
1084
+ @Path("resource/{resource_id}/share")
1085
+ @POST
1086
+ public Response shareResource(
1087
+ @PathParam("resource_id") String resourceId,
1088
+ @FormParam("user_id") String[] userIds,
1089
+ @FormParam("scope_id") String[] scopes) {
1090
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
1091
+
1092
+ if (auth == null) {
1093
+ return login("resource");
1094
+ }
1095
+
1096
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
1097
+
1098
+ csrfCheck(formData);
1099
+
1100
+ AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class);
1101
+ PermissionTicketStore ticketStore = authorization.getStoreFactory().getPermissionTicketStore();
1102
+ ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
1103
+ Resource resource =
1104
+ authorization.getStoreFactory().getResourceStore().findById(realm, null, resourceId);
1105
+ ResourceServer resourceServer = resource.getResourceServer();
1106
+
1107
+ if (resource == null) {
1108
+ throw ErrorResponse.error("Invalid resource", Response.Status.BAD_REQUEST);
1109
+ }
1110
+
1111
+ if (userIds == null || userIds.length == 0) {
1112
+ setReferrerOnPage();
1113
+ return account
1114
+ .setError(Status.BAD_REQUEST, Messages.MISSING_PASSWORD)
1115
+ .createResponse(AccountPages.PASSWORD);
1116
+ }
1117
+
1118
+ for (String id : userIds) {
1119
+ UserModel user = session.users().getUserById(realm, id);
1120
+
1121
+ if (user == null) {
1122
+ user = session.users().getUserByUsername(realm, id);
1123
+ }
1124
+
1125
+ if (user == null) {
1126
+ user = session.users().getUserByEmail(realm, id);
1127
+ }
1128
+
1129
+ if (user == null) {
1130
+ setReferrerOnPage();
1131
+ return account
1132
+ .setError(Status.BAD_REQUEST, Messages.INVALID_USER)
1133
+ .createResponse(AccountPages.RESOURCE_DETAIL);
1134
+ }
1135
+
1136
+ Map<PermissionTicket.FilterOption, String> filters =
1137
+ new EnumMap<>(PermissionTicket.FilterOption.class);
1138
+
1139
+ filters.put(PermissionTicket.FilterOption.RESOURCE_ID, resource.getId());
1140
+ filters.put(PermissionTicket.FilterOption.OWNER, auth.getUser().getId());
1141
+ filters.put(PermissionTicket.FilterOption.REQUESTER, user.getId());
1142
+
1143
+ List<PermissionTicket> tickets = ticketStore.find(realm, resourceServer, filters, null, null);
1144
+ final String userId = user.getId();
1145
+
1146
+ if (tickets.isEmpty()) {
1147
+ if (scopes != null && scopes.length > 0) {
1148
+ for (String scopeId : scopes) {
1149
+ Scope scope = scopeStore.findById(realm, resourceServer, scopeId);
1150
+ PermissionTicket ticket = ticketStore.create(resourceServer, resource, scope, userId);
1151
+ ticket.setGrantedTimestamp(System.currentTimeMillis());
1152
+ }
1153
+ } else {
1154
+ if (resource.getScopes().isEmpty()) {
1155
+ PermissionTicket ticket = ticketStore.create(resourceServer, resource, null, userId);
1156
+ ticket.setGrantedTimestamp(System.currentTimeMillis());
1157
+ } else {
1158
+ for (Scope scope : resource.getScopes()) {
1159
+ PermissionTicket ticket = ticketStore.create(resourceServer, resource, scope, userId);
1160
+ ticket.setGrantedTimestamp(System.currentTimeMillis());
1161
+ }
1162
+ }
1163
+ }
1164
+ } else if (scopes != null && scopes.length > 0) {
1165
+ List<String> grantScopes = new ArrayList<>(Arrays.asList(scopes));
1166
+ Set<String> alreadyGrantedScopes =
1167
+ tickets.stream()
1168
+ .map(PermissionTicket::getScope)
1169
+ .map(Scope::getId)
1170
+ .collect(Collectors.toSet());
1171
+
1172
+ grantScopes.removeIf(alreadyGrantedScopes::contains);
1173
+
1174
+ for (String scopeId : grantScopes) {
1175
+ Scope scope = scopeStore.findById(realm, resourceServer, scopeId);
1176
+ PermissionTicket ticket = ticketStore.create(resourceServer, resource, scope, userId);
1177
+ ticket.setGrantedTimestamp(System.currentTimeMillis());
1178
+ }
1179
+ }
1180
+ }
1181
+
1182
+ return forwardToPage("resource", AccountPages.RESOURCE_DETAIL);
1183
+ }
1184
+
1185
+ @Path("resource")
1186
+ @POST
1187
+ public Response processResourceActions(
1188
+ @FormParam("resource_id") String[] resourceIds, @FormParam("action") String action) {
1189
+ MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
1190
+
1191
+ if (auth == null) {
1192
+ return login("resource");
1193
+ }
1194
+
1195
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
1196
+ csrfCheck(formData);
1197
+
1198
+ AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class);
1199
+ PermissionTicketStore ticketStore = authorization.getStoreFactory().getPermissionTicketStore();
1200
+
1201
+ if (action == null) {
1202
+ throw ErrorResponse.error("Invalid action", Response.Status.BAD_REQUEST);
1203
+ }
1204
+
1205
+ for (String resourceId : resourceIds) {
1206
+ Resource resource =
1207
+ authorization.getStoreFactory().getResourceStore().findById(realm, null, resourceId);
1208
+
1209
+ if (resource == null) {
1210
+ throw ErrorResponse.error("Invalid resource", Response.Status.BAD_REQUEST);
1211
+ }
1212
+
1213
+ Map<PermissionTicket.FilterOption, String> filters =
1214
+ new EnumMap<>(PermissionTicket.FilterOption.class);
1215
+
1216
+ filters.put(PermissionTicket.FilterOption.REQUESTER, auth.getUser().getId());
1217
+ filters.put(PermissionTicket.FilterOption.RESOURCE_ID, resource.getId());
1218
+
1219
+ if ("cancel".equals(action)) {
1220
+ filters.put(PermissionTicket.FilterOption.GRANTED, Boolean.TRUE.toString());
1221
+ } else if ("cancelRequest".equals(action)) {
1222
+ filters.put(PermissionTicket.FilterOption.GRANTED, Boolean.FALSE.toString());
1223
+ }
1224
+
1225
+ RealmModel realm = resource.getResourceServer().getRealm();
1226
+ for (PermissionTicket ticket :
1227
+ ticketStore.find(realm, resource.getResourceServer(), filters, null, null)) {
1228
+ ticketStore.delete(realm, ticket.getId());
1229
+ }
1230
+ }
1231
+
1232
+ return forwardToPage("authorization", AccountPages.RESOURCES);
1233
+ }
1234
+
1235
+ public static UriBuilder loginRedirectUrl(UriBuilder base) {
1236
+ return RealmsResource.accountUrl(base).path(AccountFormService.class, "loginRedirect");
1237
+ }
1238
+
1239
+ @Override
1240
+ protected URI getBaseRedirectUri() {
1241
+ return AccountUrls.accountBase(session.getContext().getUri().getBaseUri())
1242
+ .path("/")
1243
+ .build(realm.getName());
1244
+ }
1245
+
1246
+ public static boolean isPasswordSet(KeycloakSession session, RealmModel realm, UserModel user) {
1247
+ return user.credentialManager().isConfiguredFor(PasswordCredentialModel.TYPE);
1248
+ }
1249
+
1250
+ private String[] getReferrer() {
1251
+ String referrer = session.getContext().getUri().getQueryParameters().getFirst("referrer");
1252
+ if (referrer == null) {
1253
+ return null;
1254
+ }
1255
+
1256
+ String referrerUri =
1257
+ session.getContext().getUri().getQueryParameters().getFirst("referrer_uri");
1258
+
1259
+ ClientModel referrerClient = realm.getClientByClientId(referrer);
1260
+ if (referrerClient != null) {
1261
+ if (referrerUri != null) {
1262
+ referrerUri = RedirectUtils.verifyRedirectUri(session, referrerUri, referrerClient);
1263
+ } else {
1264
+ referrerUri =
1265
+ ResolveRelative.resolveRelativeUri(
1266
+ session, referrerClient.getRootUrl(), referrerClient.getBaseUrl());
1267
+ }
1268
+
1269
+ if (referrerUri != null) {
1270
+ String referrerName = referrerClient.getName();
1271
+ if (Validation.isBlank(referrerName)) {
1272
+ referrerName = referrer;
1273
+ }
1274
+ return new String[] {referrerName, referrerUri};
1275
+ }
1276
+ } else if (referrerUri != null) {
1277
+ if (client != null) {
1278
+ referrerUri = RedirectUtils.verifyRedirectUri(session, referrerUri, client);
1279
+
1280
+ if (referrerUri != null) {
1281
+ return new String[] {referrer, referrerUri};
1282
+ }
1283
+ }
1284
+ }
1285
+
1286
+ return null;
1287
+ }
1288
+
1289
+ private enum AccountSocialAction {
1290
+ ADD,
1291
+ REMOVE;
1292
+
1293
+ public static AccountSocialAction getAction(String action) {
1294
+ if ("add".equalsIgnoreCase(action)) {
1295
+ return ADD;
1296
+ } else if ("remove".equalsIgnoreCase(action)) {
1297
+ return REMOVE;
1298
+ } else {
1299
+ return null;
1300
+ }
1301
+ }
1302
+ }
1303
+
1304
+ private void csrfCheck(final MultivaluedMap<String, String> formData) {
1305
+ String formStateChecker = formData.getFirst("stateChecker");
1306
+ if (formStateChecker == null || !formStateChecker.equals(this.stateChecker)) {
1307
+ throw new ForbiddenException();
1308
+ }
1309
+ }
1310
+ }