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