persist correctly session and user profile
This commit is contained in:
parent
1ad8f686df
commit
7d8205dd7a
10 changed files with 195 additions and 123 deletions
|
@ -19,6 +19,7 @@ import org.xbib.net.UserProfile;
|
||||||
import org.xbib.net.http.HttpAddress;
|
import org.xbib.net.http.HttpAddress;
|
||||||
import org.xbib.net.http.cookie.SameSite;
|
import org.xbib.net.http.cookie.SameSite;
|
||||||
import org.xbib.net.http.server.auth.BaseAttributes;
|
import org.xbib.net.http.server.auth.BaseAttributes;
|
||||||
|
import org.xbib.net.http.server.auth.PersistUserProfileHandler;
|
||||||
import org.xbib.net.http.server.route.BaseHttpRouterContext;
|
import org.xbib.net.http.server.route.BaseHttpRouterContext;
|
||||||
import org.xbib.net.http.server.HttpHandler;
|
import org.xbib.net.http.server.HttpHandler;
|
||||||
import org.xbib.net.http.server.HttpRequestBuilder;
|
import org.xbib.net.http.server.HttpRequestBuilder;
|
||||||
|
@ -178,7 +179,9 @@ public class BaseApplication implements Application {
|
||||||
}
|
}
|
||||||
httpRouterContext.addOpenHandler(newIncomingContextHandler(userProfileCodec, sessionCodec));
|
httpRouterContext.addOpenHandler(newIncomingContextHandler(userProfileCodec, sessionCodec));
|
||||||
httpRouterContext.addCloseHandler(newOutgoingContextHandler());
|
httpRouterContext.addCloseHandler(newOutgoingContextHandler());
|
||||||
httpRouterContext.addCloseHandler(newPersistHandler(userProfileCodec, sessionCodec));
|
for (HttpHandler httpHandler : newPersistHandlers(userProfileCodec, sessionCodec)) {
|
||||||
|
httpRouterContext.addCloseHandler(httpHandler);
|
||||||
|
}
|
||||||
httpRouterContext.addCloseHandler(newOutgoingCookieHandler());
|
httpRouterContext.addCloseHandler(newOutgoingCookieHandler());
|
||||||
return httpRouterContext;
|
return httpRouterContext;
|
||||||
}
|
}
|
||||||
|
@ -243,8 +246,8 @@ public class BaseApplication implements Application {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpHandler newPersistHandler(Codec<UserProfile> userProfileCodec, Codec<Session> sessionCodec) {
|
protected Collection<HttpHandler> newPersistHandlers(Codec<UserProfile> userProfileCodec, Codec<Session> sessionCodec) {
|
||||||
return new PersistSessionHandler(userProfileCodec, sessionCodec);
|
return List.of(new PersistSessionHandler(sessionCodec), new PersistUserProfileHandler(userProfileCodec));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -129,6 +129,11 @@ public class BaseUserProfile implements UserProfile {
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAttribute(String key, Object value) {
|
||||||
|
attributes.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Attributes getAttributes() {
|
public Attributes getAttributes() {
|
||||||
return attributes;
|
return attributes;
|
||||||
|
@ -137,31 +142,23 @@ public class BaseUserProfile implements UserProfile {
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> asMap() {
|
public Map<String, Object> asMap() {
|
||||||
TinyMap.Builder<String, Object> builder = TinyMap.builder();
|
TinyMap.Builder<String, Object> builder = TinyMap.builder();
|
||||||
builder.put("name", getName());
|
builder.putIfNotNull(NAME, getName());
|
||||||
String userId = getUserId();
|
builder.putIfNotNull(USER_ID, getUserId());
|
||||||
if (userId == null) {
|
builder.putIfNotNull(EFFECTIVE_USER_ID, getEffectiveUserId());
|
||||||
userId = "";
|
|
||||||
}
|
|
||||||
builder.put("user_id", userId);
|
|
||||||
String eUserId = getEffectiveUserId();
|
|
||||||
if (eUserId == null) {
|
|
||||||
eUserId = "";
|
|
||||||
}
|
|
||||||
builder.put("e_user_id", eUserId);
|
|
||||||
if (getRoles() != null && !getRoles().isEmpty()) {
|
if (getRoles() != null && !getRoles().isEmpty()) {
|
||||||
builder.put("roles", getRoles());
|
builder.put(ROLES, getRoles());
|
||||||
}
|
}
|
||||||
if (getEffectiveRoles() != null && !getEffectiveRoles().isEmpty()) {
|
if (getEffectiveRoles() != null && !getEffectiveRoles().isEmpty()) {
|
||||||
builder.put("e_roles", getEffectiveRoles());
|
builder.put(EFFECTIVE_ROLES, getEffectiveRoles());
|
||||||
}
|
}
|
||||||
if (getPermissions() != null && !getPermissions().isEmpty()) {
|
if (getPermissions() != null && !getPermissions().isEmpty()) {
|
||||||
builder.put("perms", getPermissions());
|
builder.put(PERMISSIONS, getPermissions());
|
||||||
}
|
}
|
||||||
if (getEffectivePermissions() != null && !getEffectivePermissions().isEmpty()) {
|
if (getEffectivePermissions() != null && !getEffectivePermissions().isEmpty()) {
|
||||||
builder.put("e_perms", getEffectivePermissions());
|
builder.put(EFFECTIVE_PERMISSIONS, getEffectivePermissions());
|
||||||
}
|
}
|
||||||
if (getAttributes() != null && !getAttributes().isEmpty()) {
|
if (getAttributes() != null && !getAttributes().isEmpty()) {
|
||||||
builder.put("attrs", getAttributes());
|
builder.put(ATTRIBUTES, getAttributes());
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
@ -169,39 +166,68 @@ public class BaseUserProfile implements UserProfile {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static UserProfile fromMap(Map<String, Object> map) {
|
public static UserProfile fromMap(Map<String, Object> map) {
|
||||||
BaseUserProfile userProfile = new BaseUserProfile();
|
BaseUserProfile userProfile = new BaseUserProfile();
|
||||||
if (map.containsKey("name")) {
|
if (map.containsKey(NAME)) {
|
||||||
userProfile.setName((String) map.get("name"));
|
userProfile.setName((String) map.get(NAME));
|
||||||
}
|
}
|
||||||
if (map.containsKey("user_id")) {
|
if (map.containsKey(USER_ID)) {
|
||||||
String userId = (String) map.get("user_id");
|
String userId = (String) map.get(USER_ID);
|
||||||
// empty user ID for better map transport, change it to null
|
|
||||||
if (userId != null && userId.isEmpty()) {
|
|
||||||
userId = null;
|
|
||||||
}
|
|
||||||
userProfile.setUserId(userId);
|
userProfile.setUserId(userId);
|
||||||
}
|
}
|
||||||
if (map.containsKey("e_user_id")) {
|
if (map.containsKey(EFFECTIVE_USER_ID)) {
|
||||||
String eUserId = (String) map.get("e_user_id");
|
String eUserId = (String) map.get(EFFECTIVE_USER_ID);
|
||||||
// empty effective user ID for better map transport, change it to null
|
|
||||||
if (eUserId != null && eUserId.isEmpty()) {
|
|
||||||
eUserId = null;
|
|
||||||
}
|
|
||||||
userProfile.setEffectiveUserId(eUserId);
|
userProfile.setEffectiveUserId(eUserId);
|
||||||
}
|
}
|
||||||
if (map.containsKey("roles")) {
|
if (map.containsKey(ROLES)) {
|
||||||
userProfile.setRoles((Collection<String>) map.get("roles"));
|
userProfile.setRoles((Collection<String>) map.get(ROLES));
|
||||||
}
|
}
|
||||||
if (map.containsKey("e_roles")) {
|
if (map.containsKey(EFFECTIVE_ROLES)) {
|
||||||
userProfile.setEffectiveRoles((Collection<String>) map.get("e_roles"));
|
userProfile.setEffectiveRoles((Collection<String>) map.get(EFFECTIVE_ROLES));
|
||||||
}
|
}
|
||||||
if (map.containsKey("perms")) {
|
if (map.containsKey(PERMISSIONS)) {
|
||||||
userProfile.setPermissions((Collection<String>) map.get("perms"));
|
userProfile.setPermissions((Collection<String>) map.get(PERMISSIONS));
|
||||||
}
|
}
|
||||||
if (map.containsKey("e_perms")) {
|
if (map.containsKey(EFFECTIVE_PERMISSIONS)) {
|
||||||
userProfile.setEffectivePermissions((Collection<String>) map.get("e_perms"));
|
userProfile.setEffectivePermissions((Collection<String>) map.get(EFFECTIVE_PERMISSIONS));
|
||||||
}
|
}
|
||||||
if (map.containsKey("attrs")) {
|
if (map.containsKey(ATTRIBUTES)) {
|
||||||
userProfile.setAttributes(new BaseAttributes((Map<String, Object>) map.get("attrs")));
|
userProfile.setAttributes(new BaseAttributes((Map<String, Object>) map.get(ATTRIBUTES)));
|
||||||
|
}
|
||||||
|
return userProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static UserProfile fromMap(UserProfile userProfile, Map<String, Object> map) {
|
||||||
|
if (userProfile.getName() == null && map.containsKey(NAME)) {
|
||||||
|
userProfile.setName((String) map.get(NAME));
|
||||||
|
}
|
||||||
|
if (userProfile.getUserId() == null && map.containsKey(USER_ID)) {
|
||||||
|
String userId = (String) map.get(USER_ID);
|
||||||
|
userProfile.setUserId(userId);
|
||||||
|
}
|
||||||
|
if (map.containsKey(EFFECTIVE_USER_ID)) {
|
||||||
|
String eUserId = (String) map.get(EFFECTIVE_USER_ID);
|
||||||
|
userProfile.setEffectiveUserId(eUserId);
|
||||||
|
}
|
||||||
|
if ((userProfile.getRoles() == null || userProfile.getRoles().isEmpty()) && map.containsKey(ROLES)) {
|
||||||
|
userProfile.setRoles((Collection<String>) map.get(ROLES));
|
||||||
|
}
|
||||||
|
if (map.containsKey(EFFECTIVE_ROLES)) {
|
||||||
|
userProfile.setEffectiveRoles((Collection<String>) map.get(EFFECTIVE_ROLES));
|
||||||
|
}
|
||||||
|
if ((userProfile.getPermissions() == null || userProfile.getPermissions().isEmpty()) && map.containsKey(PERMISSIONS)) {
|
||||||
|
userProfile.setPermissions((Collection<String>) map.get(PERMISSIONS));
|
||||||
|
}
|
||||||
|
if (map.containsKey(EFFECTIVE_PERMISSIONS)) {
|
||||||
|
userProfile.setEffectivePermissions((Collection<String>) map.get(EFFECTIVE_PERMISSIONS));
|
||||||
|
}
|
||||||
|
if (map.containsKey(ATTRIBUTES)) {
|
||||||
|
if (userProfile.getAttributes() == null || userProfile.getAttributes().isEmpty()) {
|
||||||
|
userProfile.setAttributes(new BaseAttributes((Map<String, Object>) map.get(ATTRIBUTES)));
|
||||||
|
} else {
|
||||||
|
for (Map.Entry<String, Object> entry : ((Map<String, Object>) map.get(ATTRIBUTES)).entrySet()) {
|
||||||
|
userProfile.updateAttribute(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return userProfile;
|
return userProfile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,19 +33,13 @@ public class FileJsonUserProfileCodec implements Codec<UserProfile> {
|
||||||
public FileJsonUserProfileCodec(Path path) {
|
public FileJsonUserProfileCodec(Path path) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.lock = new ReentrantReadWriteLock();
|
this.lock = new ReentrantReadWriteLock();
|
||||||
try {
|
|
||||||
Files.createDirectories(path);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserProfile create(String key) throws IOException {
|
public UserProfile create(String userId) throws IOException {
|
||||||
Objects.requireNonNull(key, "key must not be null");
|
Objects.requireNonNull(userId, "user id must not be null");
|
||||||
BaseUserProfile baseUserProfile = new BaseUserProfile();
|
BaseUserProfile baseUserProfile = new BaseUserProfile();
|
||||||
baseUserProfile.setUserId(key);
|
baseUserProfile.setUserId(userId);
|
||||||
return baseUserProfile;
|
return baseUserProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +64,7 @@ public class FileJsonUserProfileCodec implements Codec<UserProfile> {
|
||||||
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
|
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
|
||||||
try {
|
try {
|
||||||
writeLock.lock();
|
writeLock.lock();
|
||||||
|
createPath();
|
||||||
PercentEncoder percentEncoder = PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8);
|
PercentEncoder percentEncoder = PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8);
|
||||||
try (Writer writer = Files.newBufferedWriter(path.resolve(percentEncoder.encode(key)))) {
|
try (Writer writer = Files.newBufferedWriter(path.resolve(percentEncoder.encode(key)))) {
|
||||||
writer.write(JsonUtil.toString(userProfile.asMap()));
|
writer.write(JsonUtil.toString(userProfile.asMap()));
|
||||||
|
@ -136,4 +131,15 @@ public class FileJsonUserProfileCodec implements Codec<UserProfile> {
|
||||||
logger.log(Level.WARNING, "i/o error while destroy: " + e.getMessage(), e);
|
logger.log(Level.WARNING, "i/o error while destroy: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createPath() {
|
||||||
|
try {
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
Files.createDirectories(path);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,10 @@ public class LoginAuthenticationHandler implements HttpHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean authenticate(UserProfile userProfile, String username, String password, Request request) {
|
protected boolean authenticate(UserProfile userProfile,
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
Request request) {
|
||||||
if (username == null) {
|
if (username == null) {
|
||||||
logger.log(Level.FINE, "no username given for check, doing nothing");
|
logger.log(Level.FINE, "no username given for check, doing nothing");
|
||||||
return false;
|
return false;
|
||||||
|
@ -89,21 +92,36 @@ public class LoginAuthenticationHandler implements HttpHandler {
|
||||||
Authenticator auth = securityRealm.getAuthenticator();
|
Authenticator auth = securityRealm.getAuthenticator();
|
||||||
Authenticator.Context authContext = new Authenticator.Context(username, password, request);
|
Authenticator.Context authContext = new Authenticator.Context(username, password, request);
|
||||||
if (auth.authenticate(authContext)) {
|
if (auth.authenticate(authContext)) {
|
||||||
logger.log(Level.FINE, "authenticated, augmenting user profile with " + authContext.getUsername());
|
logger.log(Level.FINE, "authenticated as " + authContext.getUsername());
|
||||||
userProfile.setUserId(authContext.getUsername());
|
userProfile.setUserId(authContext.getUsername());
|
||||||
UsersProvider.Context userContext = new UsersProvider.Context(username, null);
|
UsersProvider.Context userContext = new UsersProvider.Context(authContext.getUsername(), null);
|
||||||
UserDetails userDetails = securityRealm.getUsersProvider().getUserDetails(userContext);
|
UserDetails userDetails = securityRealm.getUsersProvider().getUserDetails(userContext);
|
||||||
userProfile.setEffectiveUserId(userDetails.getEffectiveUserId());
|
|
||||||
userProfile.setName(userDetails.getName());
|
userProfile.setName(userDetails.getName());
|
||||||
GroupsProvider.Context groupContext = new GroupsProvider.Context(username, null);
|
if (userDetails.getEffectiveUserId() != null) {
|
||||||
|
userProfile.setEffectiveUserId(userDetails.getEffectiveUserId());
|
||||||
|
} else {
|
||||||
|
userProfile.setEffectiveUserId(authContext.getUsername());
|
||||||
|
}
|
||||||
|
GroupsProvider.Context groupContext = new GroupsProvider.Context(authContext.getUsername(), null);
|
||||||
Collection<String> groups = securityRealm.getGroupsProvider().getGroups(groupContext);
|
Collection<String> groups = securityRealm.getGroupsProvider().getGroups(groupContext);
|
||||||
for (String group : groups) {
|
for (String group : groups) {
|
||||||
userProfile.addRole(group);
|
userProfile.addRole(group);
|
||||||
}
|
}
|
||||||
logger.log(Level.FINE, "authentication success: userProfile = " + userProfile);
|
if (!userProfile.getUserId().equals(userProfile.getEffectiveUserId())) {
|
||||||
|
GroupsProvider.Context effectiveGroupContext = new GroupsProvider.Context(userProfile.getEffectiveUserId(), null);
|
||||||
|
for (String group : securityRealm.getGroupsProvider().getGroups(effectiveGroupContext)) {
|
||||||
|
userProfile.addEffectiveRole(group);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (String group : groups) {
|
||||||
|
userProfile.addEffectiveRole(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO permissions and effective permissions
|
||||||
|
logger.log(Level.FINE, "user and role setup completed for " + username);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
logger.log(Level.FINE, "authentication failure");
|
logger.log(Level.FINE, "authentication failure for user " + username);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.xbib.net.http.server.auth;
|
||||||
|
|
||||||
|
import org.xbib.net.UserProfile;
|
||||||
|
import org.xbib.net.http.HttpResponseStatus;
|
||||||
|
import org.xbib.net.http.server.HttpException;
|
||||||
|
import org.xbib.net.http.server.HttpHandler;
|
||||||
|
import org.xbib.net.http.server.persist.Codec;
|
||||||
|
import org.xbib.net.http.server.route.HttpRouterContext;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class PersistUserProfileHandler implements HttpHandler {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PersistUserProfileHandler.class.getName());
|
||||||
|
|
||||||
|
private final Codec<UserProfile> userProfileCodec;
|
||||||
|
|
||||||
|
public PersistUserProfileHandler(Codec<UserProfile> userProfileCodec) {
|
||||||
|
this.userProfileCodec = userProfileCodec;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpRouterContext context) throws IOException {
|
||||||
|
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
|
||||||
|
if (userProfile != null && userProfile.getUserId() != null) {
|
||||||
|
try {
|
||||||
|
logger.log(Level.FINEST, "writing user profile id " + userProfile.getUserId());
|
||||||
|
userProfileCodec.write(userProfile.getUserId(), userProfile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
|
throw new HttpException("unable to write user profile data", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.log(Level.FINEST, "not writing user profile " + userProfile + " user id + " +
|
||||||
|
(userProfile != null ? userProfile.getUserId() : null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,12 +46,6 @@ public class FileJsonSessionCodec implements Codec<Session> {
|
||||||
this.sessionCacheSize = sessionCacheSize;
|
this.sessionCacheSize = sessionCacheSize;
|
||||||
this.sessionDuration = sessionDuration;
|
this.sessionDuration = sessionDuration;
|
||||||
this.lock = new ReentrantReadWriteLock();
|
this.lock = new ReentrantReadWriteLock();
|
||||||
try {
|
|
||||||
Files.createDirectories(path);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -80,6 +74,7 @@ public class FileJsonSessionCodec implements Codec<Session> {
|
||||||
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
|
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
|
||||||
try {
|
try {
|
||||||
writeLock.lock();
|
writeLock.lock();
|
||||||
|
createPath();
|
||||||
PercentEncoder percentEncoder = PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8);
|
PercentEncoder percentEncoder = PercentEncoders.getUnreservedEncoder(StandardCharsets.UTF_8);
|
||||||
try (Writer writer = Files.newBufferedWriter(path.resolve(percentEncoder.encode(key)))) {
|
try (Writer writer = Files.newBufferedWriter(path.resolve(percentEncoder.encode(key)))) {
|
||||||
writer.write(JsonUtil.toString(session));
|
writer.write(JsonUtil.toString(session));
|
||||||
|
@ -146,4 +141,15 @@ public class FileJsonSessionCodec implements Codec<Session> {
|
||||||
logger.log(Level.WARNING, "i/o error while destroy: " + e.getMessage(), e);
|
logger.log(Level.WARNING, "i/o error while destroy: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createPath() {
|
||||||
|
try {
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
Files.createDirectories(path);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,14 +77,14 @@ public class IncomingContextHandler implements HttpHandler {
|
||||||
try {
|
try {
|
||||||
// extract payload from our cookie, must be not null, otherwise security problem with exception
|
// extract payload from our cookie, must be not null, otherwise security problem with exception
|
||||||
payload = toPayload(cookie);
|
payload = toPayload(cookie);
|
||||||
logger.log(Level.FINE, payload != null && !payload.isEmpty() ? "payload found" : "no payload");
|
logger.log(Level.FINE, payload != null && !payload.isEmpty() ? "payload found" : "no payload found");
|
||||||
// extract session from payload and recover session from persistence store
|
// extract session from payload and recover session from persistence store
|
||||||
session = toSession(payload);
|
session = toSession(payload);
|
||||||
// do not log explicit content of payload, session, userprofile to not leak sensitive info into log
|
// do not log explicit content of payload, session, userprofile to not leak sensitive info into log
|
||||||
logger.log(Level.FINE, session != null && !session.isEmpty() ? "session found" : "no session");
|
logger.log(Level.FINE, session != null && !session.isEmpty() ? "session found" : "no session found");
|
||||||
// extract userprofile from cookie info, use previous auth handler setup, recover attributes only
|
// extract userprofile from cookie info, use previous auth handler setup, recover attributes only
|
||||||
userProfile = recoverUserProfile(context, session, payload);
|
userProfile = recoverUserProfile(context, session, payload);
|
||||||
logger.log(Level.FINE, userProfile != null ? "user profile found" : "no user profile");
|
logger.log(Level.FINE, userProfile != null ? "user profile found" : "no user profile found");
|
||||||
} catch (CookieSignatureException e) {
|
} catch (CookieSignatureException e) {
|
||||||
// set exception in context to discard broken cookie later and render exception message
|
// set exception in context to discard broken cookie later and render exception message
|
||||||
context.getAttributes().put("_throwable", e);
|
context.getAttributes().put("_throwable", e);
|
||||||
|
@ -105,17 +105,21 @@ public class IncomingContextHandler implements HttpHandler {
|
||||||
logger.log(Level.FINE, "new session created, id = " + session.id());
|
logger.log(Level.FINE, "new session created, id = " + session.id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// important
|
||||||
context.getAttributes().put("session", session);
|
context.getAttributes().put("session", session);
|
||||||
if (userProfile == null) {
|
if (userProfile == null) {
|
||||||
try {
|
try {
|
||||||
userProfile = recoverUserProfile(context, session, payload);
|
userProfile = recoverUserProfile(context, session, payload);
|
||||||
if (userProfile != null) {
|
if (userProfile != null) {
|
||||||
logger.log(Level.FINE, "new user profile recovered");
|
logger.log(Level.FINE, "user profile recovered");
|
||||||
|
} else {
|
||||||
|
logger.log(Level.FINE, "no user profile recovered");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.log(Level.FINE, "unable to recover new user profile: " + e.getMessage(), e);
|
logger.log(Level.FINE, "unable to recover new user profile: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// important
|
||||||
context.getAttributes().put("userprofile", userProfile);
|
context.getAttributes().put("userprofile", userProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,39 +180,25 @@ public class IncomingContextHandler implements HttpHandler {
|
||||||
Session session,
|
Session session,
|
||||||
Map<String, Object> payload)
|
Map<String, Object> payload)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (payload == null && session != null) {
|
// already in context from previous handler?
|
||||||
// session has user profile? this is important for spanning HTTP request/response like in HTTP POST/FORWARD/GET
|
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
|
||||||
Map<String, Object> map = (Map<String, Object>) session.get("userprofile");
|
if (userProfile != null) {
|
||||||
if (map != null) {
|
return userProfileCodec.read(userProfile.getUserId());
|
||||||
UserProfile userProfile = BaseUserProfile.fromMap(map);
|
|
||||||
if (userProfile.getAttributes().isEmpty()) {
|
|
||||||
// recover complete user profile from codec
|
|
||||||
userProfileCodec.read(userProfile.getUserId());
|
|
||||||
}
|
|
||||||
return userProfile;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (payload != null) {
|
// initialize
|
||||||
// from cookie
|
Map<String, Object> map = null;
|
||||||
Map<String, Object> m = (Map<String, Object>) payload.get("map");
|
// from session?
|
||||||
if (m != null && m.containsKey("user_id")) {
|
if (payload == null && session != null) {
|
||||||
String key = (String) m.get("user_id");
|
// session has stored user profile? this is important for spanning HTTP request/response like in HTTP POST/FORWARD/GET
|
||||||
logger.log(Level.INFO, "recover: user id = " + key);
|
map = (Map<String, Object>) session.get("userprofile");
|
||||||
// set by previous handler?
|
} else if (payload != null) {
|
||||||
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
|
// otherwise from cookie?
|
||||||
if (userProfile == null) {
|
map = (Map<String, Object>) payload.get("map");
|
||||||
userProfile = new BaseUserProfile();
|
}
|
||||||
}
|
if (map != null && map.containsKey(UserProfile.USER_ID)) {
|
||||||
userProfile.setUserId(key);
|
return userProfileCodec.read(BaseUserProfile.fromMap(map).getUserId());
|
||||||
if (m.containsKey("e_user_id")) {
|
} else {
|
||||||
userProfile.setEffectiveUserId((String) m.get("e_user_id"));
|
logger.log(Level.FINE, "unable to find info for initialize user profile");
|
||||||
}
|
|
||||||
if (userProfile.getAttributes().isEmpty()) {
|
|
||||||
// recover complete user profile from codec
|
|
||||||
userProfileCodec.read(key);
|
|
||||||
}
|
|
||||||
return userProfile;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,8 +130,8 @@ public class OutgoingContextHandler implements HttpHandler {
|
||||||
}
|
}
|
||||||
TinyMap.Builder<String, Object> builder = TinyMap.builder();
|
TinyMap.Builder<String, Object> builder = TinyMap.builder();
|
||||||
if (userProfile != null) {
|
if (userProfile != null) {
|
||||||
builder.putIfNotNull("user_id", userProfile.getUserId());
|
builder.putIfNotNull(UserProfile.USER_ID, userProfile.getUserId());
|
||||||
builder.putIfNotNull("e_user_id", userProfile.getEffectiveUserId());
|
builder.putIfNotNull(UserProfile.EFFECTIVE_USER_ID, userProfile.getEffectiveUserId());
|
||||||
}
|
}
|
||||||
logger.log(Level.FINEST, "map for cookie payload = " + builder.build());
|
logger.log(Level.FINEST, "map for cookie payload = " + builder.build());
|
||||||
String payload = CookieSignatureUtil.toString(builder.build());
|
String payload = CookieSignatureUtil.toString(builder.build());
|
||||||
|
|
|
@ -15,33 +15,16 @@ public class PersistSessionHandler implements HttpHandler {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(PersistSessionHandler.class.getName());
|
private static final Logger logger = Logger.getLogger(PersistSessionHandler.class.getName());
|
||||||
|
|
||||||
private final Codec<UserProfile> userProfileCodec;
|
|
||||||
|
|
||||||
private final Codec<Session> sessionCodec;
|
private final Codec<Session> sessionCodec;
|
||||||
|
|
||||||
public PersistSessionHandler(Codec<UserProfile> userProfileCodec,
|
public PersistSessionHandler(Codec<Session> sessionCodec) {
|
||||||
Codec<Session> sessionCodec) {
|
|
||||||
this.userProfileCodec = userProfileCodec;
|
|
||||||
this.sessionCodec = sessionCodec;
|
this.sessionCodec = sessionCodec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(HttpRouterContext context) throws IOException {
|
public void handle(HttpRouterContext context) throws IOException {
|
||||||
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
|
|
||||||
if (userProfile != null && userProfile.getUserId() != null) {
|
|
||||||
try {
|
|
||||||
logger.log(Level.FINEST, "writing user profile id " + userProfile.getUserId());
|
|
||||||
userProfileCodec.write(userProfile.getUserId(), userProfile);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
|
||||||
throw new HttpException("unable to write user profile data", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.log(Level.FINEST, "not writing user profile " + userProfile + " user id + " +
|
|
||||||
(userProfile != null ? userProfile.getUserId() : null));
|
|
||||||
}
|
|
||||||
Session session = context.getAttributes().get(Session.class, "session");
|
Session session = context.getAttributes().get(Session.class, "session");
|
||||||
if (session != null) {
|
if (session != null && session.hasPayload()) {
|
||||||
try {
|
try {
|
||||||
logger.log(Level.FINEST, "writing session id " + session.id() + " keys = " + session.keySet());
|
logger.log(Level.FINEST, "writing session id " + session.id() + " keys = " + session.keySet());
|
||||||
sessionCodec.write(session.id(), session);
|
sessionCodec.write(session.id(), session);
|
||||||
|
|
|
@ -6,7 +6,7 @@ dependencyResolutionManagement {
|
||||||
version('netty', '4.1.110.Final')
|
version('netty', '4.1.110.Final')
|
||||||
version('netty-tcnative', '2.0.65.Final')
|
version('netty-tcnative', '2.0.65.Final')
|
||||||
version('datastructures', '5.1.0')
|
version('datastructures', '5.1.0')
|
||||||
version('net', '4.6.0')
|
version('net', '4.7.0')
|
||||||
library('netty-codec-http2', 'io.netty', 'netty-codec-http2').versionRef('netty')
|
library('netty-codec-http2', 'io.netty', 'netty-codec-http2').versionRef('netty')
|
||||||
library('netty-handler', 'io.netty', 'netty-handler').versionRef('netty')
|
library('netty-handler', 'io.netty', 'netty-handler').versionRef('netty')
|
||||||
library('netty-handler-proxy', 'io.netty', 'netty-handler-proxy').versionRef('netty')
|
library('netty-handler-proxy', 'io.netty', 'netty-handler-proxy').versionRef('netty')
|
||||||
|
|
Loading…
Reference in a new issue