package org.pac4j.vertx.auth; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.vertx.ext.auth.AbstractUser; import io.vertx.ext.auth.AuthProvider; import org.pac4j.core.profile.CommonProfile; import org.pac4j.vertx.core.DefaultJsonConverter; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import static java.util.stream.Collectors.toMap; /** * @author Jeremy Prime * @since 2.0.0 */ public class Pac4jUser extends AbstractUser { private final LinkedHashMap<String, CommonProfile> profiles = new LinkedHashMap<>(); private JsonObject principal; public Pac4jUser() { // I think this noop default constructor is required for deserialization from a clustered session } @Override protected void doIsPermitted(String permission, Handler<AsyncResult<Boolean>> resultHandler) { /* * Assume permitted if any profile is permitted */ resultHandler.handle(Future.succeededFuture( profiles.values().stream() .anyMatch(p -> p.getPermissions().contains(permission)) )); } @Override public JsonObject principal() { return principal; } @Override public void setAuthProvider(AuthProvider authProvider) { } @Override public void writeToBuffer(Buffer buff) { super.writeToBuffer(buff); // Now write the remainder of our stuff to the buffer; final JsonObject profilesAsJson = new JsonObject(); profiles.forEach((name, profile) -> { final JsonObject profileAsJson = (JsonObject) DefaultJsonConverter.getInstance().encodeObject(profile); profilesAsJson.put(name, profileAsJson); }); final String json = profilesAsJson.toString(); byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); buff.appendInt(jsonBytes.length) .appendBytes(jsonBytes); } @Override public int readFromBuffer(int pos, Buffer buffer) { int posLocal = super.readFromBuffer(pos, buffer); final int jsonByteCount = buffer.getInt(posLocal); posLocal += 4; final byte[] jsonBytes = buffer.getBytes(posLocal, posLocal + jsonByteCount); posLocal += jsonByteCount; final String json = new String(jsonBytes, StandardCharsets.UTF_8); final JsonObject profiles = new JsonObject(json); final Map<String, CommonProfile> decodedUserProfiles = profiles.stream() .filter(e -> e.getValue() instanceof JsonObject) .map(e -> new MappedPair<>(e.getKey(), (CommonProfile) DefaultJsonConverter.getInstance().decodeObject(e.getValue()))) .collect(toMap(e -> e.key, e -> e.value)); setUserProfiles(decodedUserProfiles); return posLocal; } public Map<String, CommonProfile> pac4jUserProfiles() { return profiles; } public void setUserProfile(final String clientName, final CommonProfile profile, final boolean multiProfile) { if (!multiProfile) { profiles.clear(); } profiles.put(clientName, profile); updatePrincipal(); } private void setUserProfiles(final Map<String, CommonProfile> userProfiles) { Objects.requireNonNull(userProfiles); profiles.clear(); profiles.putAll(userProfiles); updatePrincipal(); } /** * Update the principal, to be called on any modification of the profiles map internally. */ private void updatePrincipal() { principal = new JsonObject(); profiles.forEach((name, profile) -> { final JsonObject jsonProfile = new JsonObject(); profile.getAttributes() .forEach((attributeName, attributeValue) -> jsonProfile.put(attributeName, attributeValue.toString())); principal.put(name, jsonProfile); }); } private static class MappedPair<T, U> { public final T key; public final U value; public MappedPair(final T key, final U value) { this.key = key; this.value = value; } } }