1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.frontend.jersey;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.StringWriter;
24 import java.nio.file.Files;
25 import java.nio.file.StandardOpenOption;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.SecureRandom;
28 import java.util.Date;
29 import java.util.Optional;
30
31 import org.apache.logging.log4j.LogManager;
32 import org.mycore.common.MCRSession;
33 import org.mycore.common.MCRUserInformation;
34 import org.mycore.common.config.MCRConfiguration2;
35 import org.mycore.common.config.MCRConfigurationDir;
36 import org.mycore.common.config.MCRConfigurationException;
37 import org.mycore.common.events.MCRStartupHandler;
38
39 import com.auth0.jwt.JWT;
40 import com.auth0.jwt.JWTCreator;
41 import com.auth0.jwt.algorithms.Algorithm;
42 import com.fasterxml.jackson.core.JsonFactory;
43 import com.fasterxml.jackson.core.JsonGenerator;
44
45 import jakarta.servlet.ServletContext;
46 import jakarta.ws.rs.core.Response;
47
48 public class MCRJWTUtil implements MCRStartupHandler.AutoExecutable {
49 public static final String JWT_CLAIM_ROLES = "mcr:roles";
50
51 public static final String JWT_CLAIM_IP = "mcr:ip";
52
53 public static final String JWT_USER_ATTRIBUTE_PREFIX = "mcr:ua:";
54
55 public static final String JWT_SESSION_ATTRIBUTE_PREFIX = "mcr:sa:";
56
57 private static final JsonFactory JSON_FACTORY = new JsonFactory();
58
59 private static final String ROLES_PROPERTY = "MCR.Rest.JWT.Roles";
60
61 private static Algorithm SHARED_SECRET;
62
63 public static JWTCreator.Builder getJWTBuilder(MCRSession mcrSession) {
64 return getJWTBuilder(mcrSession, null, null);
65 }
66
67 public static JWTCreator.Builder getJWTBuilder(MCRSession mcrSession,
68 String[] userAttributes, String[] sessionAttributes) {
69 MCRUserInformation userInformation = mcrSession.getUserInformation();
70 String[] roles = MCRConfiguration2.getOrThrow(ROLES_PROPERTY, MCRConfiguration2::splitValue)
71 .filter(userInformation::isUserInRole)
72 .toArray(String[]::new);
73 String subject = userInformation.getUserID();
74 String email = userInformation.getUserAttribute(MCRUserInformation.ATT_EMAIL);
75 String name = userInformation.getUserAttribute(MCRUserInformation.ATT_REAL_NAME);
76 JWTCreator.Builder builder = JWT.create()
77 .withIssuedAt(new Date())
78 .withSubject(subject)
79 .withArrayClaim("mcr:roles", roles)
80 .withClaim("email", email)
81 .withClaim("name", name);
82 if (userAttributes != null) {
83 for (String userAttribute : userAttributes) {
84 String value = userInformation.getUserAttribute(userAttribute);
85 if (value != null) {
86 builder.withClaim(JWT_USER_ATTRIBUTE_PREFIX + userAttribute, value);
87 }
88 }
89 }
90 if (sessionAttributes != null) {
91 for (String sessionAttribute : sessionAttributes) {
92 Object object = mcrSession.get(sessionAttribute);
93 Optional.ofNullable(object).map(Object::toString).ifPresent((value) -> {
94 builder.withClaim(JWT_SESSION_ATTRIBUTE_PREFIX + sessionAttribute, value);
95 });
96 }
97 }
98 return builder;
99 }
100
101 public static Algorithm getJWTAlgorithm() {
102 return SHARED_SECRET;
103 }
104
105 public static Response getJWTLoginSuccessResponse(String jwt) throws IOException {
106 try (StringWriter sw = new StringWriter()) {
107 JsonGenerator jsonGenerator = JSON_FACTORY.createGenerator(sw);
108 jsonGenerator.writeStartObject();
109 jsonGenerator.writeBooleanField("login_success", true);
110 jsonGenerator.writeStringField("access_token", jwt);
111 jsonGenerator.writeStringField("token_type", "Bearer");
112 jsonGenerator.writeEndObject();
113 jsonGenerator.flush();
114 jsonGenerator.close();
115 return Response.status(Response.Status.OK)
116 .header("Authorization", "Bearer " + jwt)
117 .entity(sw.toString())
118 .build();
119 }
120 }
121
122 public static Response getJWTRenewSuccessResponse(String jwt) throws IOException {
123 try (StringWriter sw = new StringWriter()) {
124 JsonGenerator jsonGenerator = JSON_FACTORY.createGenerator(sw);
125 jsonGenerator.writeStartObject();
126 jsonGenerator.writeBooleanField("executed", true);
127 jsonGenerator.writeStringField("access_token", jwt);
128 jsonGenerator.writeStringField("token_type", "Bearer");
129 jsonGenerator.writeEndObject();
130 jsonGenerator.flush();
131 jsonGenerator.close();
132 return Response.status(Response.Status.OK)
133 .header("Authorization", "Bearer " + jwt)
134 .entity(sw.toString())
135 .build();
136 }
137 }
138
139 public static Response getJWTLoginErrorResponse(String errorDescription) throws IOException {
140 try (StringWriter sw = new StringWriter()) {
141 JsonGenerator jsonGenerator = JSON_FACTORY.createGenerator(sw);
142 jsonGenerator.writeStartObject();
143 jsonGenerator.writeBooleanField("login_success", false);
144 jsonGenerator.writeStringField("error", "login_failed");
145 jsonGenerator.writeStringField("error_description", errorDescription);
146 jsonGenerator.writeEndObject();
147 jsonGenerator.flush();
148 jsonGenerator.close();
149 return Response.status(Response.Status.FORBIDDEN)
150 .entity(sw.toString())
151 .build();
152 }
153 }
154
155 @Override
156 public String getName() {
157 return "JSON WebToken Services";
158 }
159
160 @Override
161 public int getPriority() {
162 return 0;
163 }
164
165 @Override
166 public void startUp(ServletContext servletContext) {
167 if (servletContext != null) {
168 File sharedSecretFile = MCRConfigurationDir.getConfigFile("jwt.secret");
169 byte[] secret;
170 if (!sharedSecretFile.isFile()) {
171 secret = new byte[4096];
172 try {
173 LogManager.getLogger().warn(
174 "Creating shared secret file ({}) for JSON Web Token."
175 + " This may take a while. Please wait...",
176 sharedSecretFile);
177 SecureRandom.getInstanceStrong().nextBytes(secret);
178 Files.write(sharedSecretFile.toPath(), secret, StandardOpenOption.CREATE_NEW);
179 } catch (NoSuchAlgorithmException | IOException e) {
180 throw new MCRConfigurationException(
181 "Could not create shared secret in file: " + sharedSecretFile.getAbsolutePath(), e);
182 }
183 } else {
184 try {
185 secret = Files.readAllBytes(sharedSecretFile.toPath());
186 } catch (IOException e) {
187 throw new MCRConfigurationException(
188 "Could not create shared secret in file: " + sharedSecretFile.getAbsolutePath(), e);
189 }
190 }
191 SHARED_SECRET = Algorithm.HMAC512(secret);
192 }
193 }
194
195 }