diff --git a/solutions.digamma.damas.api/src/main/java/module-info.java b/solutions.digamma.damas.api/src/main/java/module-info.java index a5992ae5..4264c110 100644 --- a/solutions.digamma.damas.api/src/main/java/module-info.java +++ b/solutions.digamma.damas.api/src/main/java/module-info.java @@ -10,6 +10,5 @@ exports solutions.digamma.damas.search; exports solutions.digamma.damas.common; exports solutions.digamma.damas.user; - exports solutions.digamma.damas.login; exports solutions.digamma.damas.session; -} \ No newline at end of file +} diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/login/LoginManager.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/login/LoginManager.java deleted file mode 100644 index a9695346..00000000 --- a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/login/LoginManager.java +++ /dev/null @@ -1,25 +0,0 @@ -package solutions.digamma.damas.login; - -import solutions.digamma.damas.common.WorkspaceException; - -/** - * Username/password login manager. - * - * @author Ahmad Shahwan - */ -public interface LoginManager { - - /** - * Check user credentials and grant them a user session if credentials are - * valid. If credential are not usable, throw exception. - * - * @param username - * @param password - * @return - * @throws WorkspaceException - */ - Token login(String username, String password) - throws WorkspaceException; - - void logout(Token token) throws WorkspaceException; -} diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Connection.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Connection.java new file mode 100644 index 00000000..2058f12c --- /dev/null +++ b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Connection.java @@ -0,0 +1,49 @@ +package solutions.digamma.damas.session; + +import solutions.digamma.damas.common.WorkspaceException; + +/** + * A connection is a set of operations that are executed with the same user + * session. A part from login, a valid connection is required for all workspace + * operations. + * + * To create a connection, a valid used token is required, thus a valid login. + * An instance of this class can only be obtained by calling the method {@code + * connect} of a {@link ConnectionManager}. + * + * Once a connection is created, workspace methods called in the same thread + * are done in behalf of the user session that created the connection, until the + * connection is closed or another connection is created. + * + * Connections are closable. The user is encourage to close the connection as + * soon as possible to avoid security breaches. The best way to use connections + * is as a resource in a try-with-resource structure. + * + * A valid token (thus a valid user session) can be used to create as many + * connections as needed, as long as those connections are property closed. + * + * A connection can be committed or rolled back as many times as needed. + */ +public interface Connection extends AutoCloseable { + + /** + * Commit connection, persisting all changed in the user session. + * + * @throws WorkspaceException when a workspace exception occurs + */ + void commit() throws WorkspaceException; + + /** + * Rollback connection, resetting session data to the persisted state. + * + * @throws WorkspaceException when a workspace exception occurs + */ + void rollback() throws WorkspaceException; + + /** + * Close connection. + * + * @throws WorkspaceException when a workspace exception occurs + */ + void close() throws WorkspaceException; +} diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/ConnectionManager.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/ConnectionManager.java new file mode 100644 index 00000000..76d88184 --- /dev/null +++ b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/ConnectionManager.java @@ -0,0 +1,24 @@ +package solutions.digamma.damas.session; + +import solutions.digamma.damas.common.WorkspaceException; + +/** + * Connection manager. + * + * A service class used to create a new connection. + */ +public interface ConnectionManager { + + /** + * Initiate a new connection. + * + * This method is the only way to acquire a new valid workspace connection. + * + * To create a new connection a valid (authenticated) token is required. + * + * @param token authentication token + * @return workspace connection + * @throws WorkspaceException when a workspace exception occurs + */ + Connection connect(Token token) throws WorkspaceException; +} diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/LoginManager.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/LoginManager.java new file mode 100644 index 00000000..77fa6ade --- /dev/null +++ b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/LoginManager.java @@ -0,0 +1,33 @@ +package solutions.digamma.damas.session; + +import solutions.digamma.damas.common.WorkspaceException; + +/** + * Username/password login manager. + * + * @author Ahmad Shahwan + */ +public interface LoginManager { + + /** + * Check user credentials and grant them a user session if credentials are + * valid. If credential are not usable, throw exception. + * + * @param username username + * @param password password + * @return a token representing user session + * @throws WorkspaceException when credential are not valid, or another + * error occurres + */ + UserToken login(String username, String password) throws WorkspaceException; + + UserSession identify(Token token) throws WorkspaceException; + + /** + * Log out previously logged user, using their token. + * + * @param token user token + * @throws WorkspaceException if an error occurres + */ + void logout(Token token) throws WorkspaceException; +} diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/login/Token.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Token.java similarity index 52% rename from solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/login/Token.java rename to solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Token.java index c1a95be9..ca98e23b 100644 --- a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/login/Token.java +++ b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Token.java @@ -1,7 +1,7 @@ -package solutions.digamma.damas.login; +package solutions.digamma.damas.session; /** - * User session token. + * User token holding session secret. * * @author Ahmad Shahwan */ diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Transaction.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Transaction.java deleted file mode 100644 index 4f607714..00000000 --- a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/Transaction.java +++ /dev/null @@ -1,49 +0,0 @@ -package solutions.digamma.damas.session; - -import solutions.digamma.damas.common.WorkspaceException; - -/** - * A transaction is a set of operations that are executed with the same user - * session. A part from login, a transaction is required for all workspace - * operations. - * - * To begin a transaction, a valid used token is required, thus a valid login. - * An instance of this class can only be obtained by calling the method - * begin of a TransactionManager. - * - * Once a transaction has begun, workspace methods called in the current thread - * are done in behalf of the user session that began the transaction, until the - * transaction is closed or another transaction is begun. - * - * Transactions are closable. The user is encourage to close the transaction as - * soon as possible to avoid security breaches. The best way to use transactions - * is as a resource in a try-with-resource structure. - * - * A valid toke (thus a valid user session) can be used to begin as many - * transactions as needed, as long as those transactions are property closed. - * - * A transaction can be committed or rolled back as many times as needed. - */ -public interface Transaction extends AutoCloseable { - - /** - * Commit transaction, persisting all changed in the user session. - * - * @throws WorkspaceException when a workspace exception occurs - */ - void commit() throws WorkspaceException; - - /** - * Rollback transaction, resetting session data to the persisted state. - * - * @throws WorkspaceException when a workspace exception occurs - */ - void rollback() throws WorkspaceException; - - /** - * Close transaction. - * - * @throws WorkspaceException when a workspace exception occurs - */ - void close() throws WorkspaceException; -} diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/TransactionManager.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/TransactionManager.java deleted file mode 100644 index 115fad36..00000000 --- a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/TransactionManager.java +++ /dev/null @@ -1,25 +0,0 @@ -package solutions.digamma.damas.session; - -import solutions.digamma.damas.common.WorkspaceException; -import solutions.digamma.damas.login.Token; - -/** - * Transaction manager. - * - * A service class used to create and begin transactions. - */ -public interface TransactionManager { - - /** - * Instantiate and begin a transaction. - * - * This method is the only way to acquire a valid workspace transaction. - * - * To create a new transaction a valid (authenticated) token is required. - * - * @param token authentication token - * @return workspace transaction - * @throws WorkspaceException when a workspace exception occurs - */ - Transaction begin(Token token) throws WorkspaceException; -} diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/UserSession.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/UserSession.java new file mode 100644 index 00000000..00f3326f --- /dev/null +++ b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/UserSession.java @@ -0,0 +1,17 @@ +package solutions.digamma.damas.session; + +import java.util.Date; + +/** + * Information about current used session. + * + * An object of this type is returned when the who-am-I API is queried. + */ +public interface UserSession { + + String getUserLogin(); + + Date getCreationDate(); + + Date getExpirationDate(); +} diff --git a/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/UserToken.java b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/UserToken.java new file mode 100644 index 00000000..f3278de7 --- /dev/null +++ b/solutions.digamma.damas.api/src/main/java/solutions/digamma/damas/session/UserToken.java @@ -0,0 +1,11 @@ +package solutions.digamma.damas.session; + +/** + * A used session information along side a session token. + * + * An object of this type is returned upon successful authentication. Both + * session information and token are usually needed for actions following once + * user is logged in. + */ +public interface UserToken extends UserSession, Token { +} diff --git a/solutions.digamma.damas.commons/src/main/java/solutions/digamma/damas/auth/AbstractLoginModule.java b/solutions.digamma.damas.commons/src/main/java/solutions/digamma/damas/auth/AbstractLoginModule.java index 91b10559..0e933077 100644 --- a/solutions.digamma.damas.commons/src/main/java/solutions/digamma/damas/auth/AbstractLoginModule.java +++ b/solutions.digamma.damas.commons/src/main/java/solutions/digamma/damas/auth/AbstractLoginModule.java @@ -1,5 +1,7 @@ package solutions.digamma.damas.auth; +import java.util.HashSet; +import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; @@ -24,6 +26,7 @@ public abstract class AbstractLoginModule implements LoginModule { protected String login; protected char[] password; protected List roles; + protected Set credentials; private Subject subject; private boolean success; @@ -44,6 +47,7 @@ public void initialize( this.sharedState = sharedState; this.extractCredentials(); this.roles = new ArrayList<>(); + this.credentials = new HashSet<>(); } @Override @@ -59,6 +63,7 @@ public boolean commit() throws LoginException { Principal principal = () -> this.login; this.subject.getPrincipals().add(principal); this.subject.getPrincipals().addAll(this.roles); + this.subject.getPublicCredentials().addAll(this.credentials); return true; } diff --git a/solutions.digamma.damas.http.grizzly/src/test/resources/META-INF/beans.xml b/solutions.digamma.damas.http.grizzly/src/test/resources/META-INF/beans.xml new file mode 100644 index 00000000..97571117 --- /dev/null +++ b/solutions.digamma.damas.http.grizzly/src/test/resources/META-INF/beans.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/solutions.digamma.damas.http/src/test/resources/META-INF/beans.xml b/solutions.digamma.damas.http/src/test/resources/META-INF/beans.xml new file mode 100644 index 00000000..97571117 --- /dev/null +++ b/solutions.digamma.damas.http/src/test/resources/META-INF/beans.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/login/JcrLoginManager.kt b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/login/JcrLoginManager.kt index 75d37eda..704e96f9 100644 --- a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/login/JcrLoginManager.kt +++ b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/login/JcrLoginManager.kt @@ -1,14 +1,17 @@ package solutions.digamma.damas.jcr.login -import solutions.digamma.damas.login.LoginManager -import solutions.digamma.damas.login.Token +import solutions.digamma.damas.common.NotFoundException +import solutions.digamma.damas.session.LoginManager +import solutions.digamma.damas.session.Token import solutions.digamma.damas.common.WorkspaceException import solutions.digamma.damas.jcr.common.Exceptions import solutions.digamma.damas.jcr.session.SecureToken import solutions.digamma.damas.jcr.session.SessionBookkeeper import solutions.digamma.damas.jcr.session.TransactionalSession import solutions.digamma.damas.logging.Logged +import solutions.digamma.damas.session.UserSession +import solutions.digamma.damas.session.UserToken import java.util.logging.Logger import javax.inject.Inject import javax.inject.Singleton @@ -41,13 +44,16 @@ internal open class JcrLoginManager : LoginManager { username, password.toCharArray()) val jcrSession = this.repository.login(credentials) this.logger.info("Login successful.") - val token = SecureToken() - val session = TransactionalSession(jcrSession) + val token = SecureToken(username) + val session = TransactionalSession(jcrSession, username) this.bookkeeper.register(token, session) this.logger.info("Session registered.") token } + @Throws(NotFoundException::class) + override fun identify(token: Token) = this.bookkeeper.lookup(token) + @Logged @Throws(WorkspaceException::class) override fun logout(token: Token) = this.bookkeeper.unregister(token) diff --git a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/login/UserLoginModule.kt b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/login/UserLoginModule.kt index e0489907..e4a76901 100644 --- a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/login/UserLoginModule.kt +++ b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/login/UserLoginModule.kt @@ -10,6 +10,7 @@ import solutions.digamma.damas.jcr.user.JcrUser import java.security.Principal import java.util.Arrays import javax.jcr.PathNotFoundException +import javax.jcr.SimpleCredentials import javax.security.auth.login.AccountLockedException import javax.security.auth.login.AccountNotFoundException import javax.security.auth.login.CredentialNotFoundException @@ -46,6 +47,7 @@ internal open class UserLoginModule : AbstractLoginModule() { throw FailedLoginException("Invalid password") this.roles.addAll(user.memberships.map { Principal { it } }) this.roles.add(SystemRole.READWRITE) + this.credentials.add(SimpleCredentials(this.login, CharArray(0))) return true } catch (e: WorkspaceException) { throw LoginException("Authentication error") diff --git a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/model/JcrManager.kt b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/model/JcrManager.kt index 38ae1d6d..f6bd4c34 100644 --- a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/model/JcrManager.kt +++ b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/model/JcrManager.kt @@ -2,7 +2,7 @@ package solutions.digamma.damas.jcr.model import solutions.digamma.damas.common.AuthenticationException import solutions.digamma.damas.common.NotFoundException -import solutions.digamma.damas.jcr.session.JcrTransaction +import solutions.digamma.damas.jcr.session.JcrConnection import solutions.digamma.damas.jcr.session.JcrSessionConsumer import solutions.digamma.damas.logging.Logged import java.util.logging.Logger @@ -23,7 +23,7 @@ internal abstract class JcrManager : JcrSessionConsumer { @Logged @Throws(AuthenticationException::class) get() = try { - JcrTransaction.get().getSession() + JcrConnection.get().getSession() } catch (e: NotFoundException) { throw AuthenticationException(e) } diff --git a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrTransaction.kt b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrConnection.kt similarity index 78% rename from solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrTransaction.kt rename to solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrConnection.kt index 51d9a38c..0fea2e54 100644 --- a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrTransaction.kt +++ b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrConnection.kt @@ -2,20 +2,19 @@ package solutions.digamma.damas.jcr.session import solutions.digamma.damas.common.NotFoundException import solutions.digamma.damas.common.WorkspaceException -import solutions.digamma.damas.session.Transaction +import solutions.digamma.damas.session.Connection import java.util.EmptyStackException import java.util.Stack -internal class JcrTransaction : Transaction { - +internal class JcrConnection @Throws(WorkspaceException::class) constructor( val session: TransactionalSession +) : Connection { + var closed = false - @Throws(WorkspaceException::class) - constructor(session: TransactionalSession) { - this.session = session + init { this.session.acquire() - JcrTransaction.SESSION.get().push(this.session) + JcrConnection.SESSION.get().push(this.session) } @Throws(WorkspaceException::class) @@ -35,7 +34,7 @@ internal class JcrTransaction : Transaction { throw AlreadyClosedException() } this.closed = true - JcrTransaction.SESSION.get().pop() + JcrConnection.SESSION.get().pop() this.session.commit() this.session.release() } @@ -59,4 +58,4 @@ internal class NoAuthenticationFountException : NotFoundException("No authentication found in context.") internal class AlreadyClosedException() : - IllegalStateException("Session already closed.") \ No newline at end of file + IllegalStateException("Session already closed.") diff --git a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrTransactionManager.kt b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrConnectionManager.kt similarity index 60% rename from solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrTransactionManager.kt rename to solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrConnectionManager.kt index 58ab0e8e..7615bb6c 100644 --- a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrTransactionManager.kt +++ b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/JcrConnectionManager.kt @@ -3,24 +3,24 @@ package solutions.digamma.damas.jcr.session import solutions.digamma.damas.common.AuthenticationException import solutions.digamma.damas.common.NotFoundException import solutions.digamma.damas.common.WorkspaceException -import solutions.digamma.damas.session.Transaction -import solutions.digamma.damas.session.TransactionManager -import solutions.digamma.damas.login.Token +import solutions.digamma.damas.session.Connection +import solutions.digamma.damas.session.ConnectionManager +import solutions.digamma.damas.session.Token import javax.inject.Inject import javax.inject.Singleton @Singleton -internal open class JcrTransactionManager : TransactionManager { +internal open class JcrConnectionManager : ConnectionManager { @Inject private lateinit var bookkeepr: SessionBookkeeper @Throws(WorkspaceException::class) - override fun begin(token: Token?): Transaction { + override fun connect(token: Token): Connection { try { - return JcrTransaction(this.bookkeepr.lookup(token)) + return JcrConnection(this.bookkeepr.lookup(token)) } catch (e: NotFoundException) { throw AuthenticationException(e) } } -} \ No newline at end of file +} diff --git a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/SecureToken.kt b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/SecureToken.kt index ff65a344..d92c4ee8 100644 --- a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/SecureToken.kt +++ b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/SecureToken.kt @@ -1,24 +1,29 @@ package solutions.digamma.damas.jcr.session -import solutions.digamma.damas.login.Token +import solutions.digamma.damas.session.UserToken import java.math.BigInteger import java.security.SecureRandom +import java.util.Date /** * Statistically secure token. * * @author Ahmad Shahwan */ -internal class SecureToken : Token { +internal class SecureToken( + private val login: String, +) : UserToken { private val token: String + private val creation: Date /** * Constructor. */ init { this.token = SecureToken.nextToken() + this.creation = Date() } override fun getSecret() = this.token @@ -30,7 +35,9 @@ internal class SecureToken : Token { * @return equality */ override fun equals(other: Any?): Boolean = - other is Token && this.secret == other.secret + other is UserToken && + this.secret == other.secret && + this.userLogin == other.userLogin override fun hashCode(): Int { return this.secret.hashCode() @@ -44,4 +51,10 @@ internal class SecureToken : Token { return BigInteger(132, RANDOM).toString(64) } } + + override fun getUserLogin() = this.login + + override fun getCreationDate() = this.creation + + override fun getExpirationDate() = Date(Long.MAX_VALUE) } diff --git a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/SessionBookkeeper.kt b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/SessionBookkeeper.kt index 54927acc..3359f6b3 100644 --- a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/SessionBookkeeper.kt +++ b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/SessionBookkeeper.kt @@ -1,7 +1,7 @@ package solutions.digamma.damas.jcr.session import solutions.digamma.damas.common.CompatibilityException -import solutions.digamma.damas.login.Token +import solutions.digamma.damas.session.Token import solutions.digamma.damas.common.ConflictException import solutions.digamma.damas.common.NotFoundException @@ -75,10 +75,7 @@ internal class SessionBookkeeper { * @throws NotFoundException */ @Throws(NotFoundException::class) - fun lookup(token: Token?): TransactionalSession { - if (token == null) { - throw TokenIsNullException() - } + fun lookup(token: Token): TransactionalSession { val session = this.sessions[token.secret] if (session == null) { this.logger.info { "Token not found ${token.secret}." } @@ -88,10 +85,7 @@ internal class SessionBookkeeper { } } -class IncompatibleTokenException : - CompatibilityException("Incompatible token.") class TokenAlreadyExistsException : ConflictException("Token already exists.") class NoSessionForTokenException : NotFoundException("No session for the given token.") -class TokenIsNullException : NotFoundException("Token is null.") diff --git a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/TransactionalSession.kt b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/TransactionalSession.kt index 5e785eb8..0760aa6d 100644 --- a/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/TransactionalSession.kt +++ b/solutions.digamma.damas.jcr/src/main/kotlin/solutions/digamma/damas/jcr/session/TransactionalSession.kt @@ -4,6 +4,8 @@ import solutions.digamma.damas.common.WorkspaceException import solutions.digamma.damas.common.InternalStateException import solutions.digamma.damas.common.ResourceBusyException import solutions.digamma.damas.jcr.common.Exceptions +import solutions.digamma.damas.session.UserSession +import java.util.Date import javax.jcr.Session import java.util.concurrent.TimeUnit @@ -15,9 +17,13 @@ import java.util.concurrent.locks.ReentrantLock * @param session JCR session. * @author Ahmad Shahwan */ -internal class TransactionalSession -internal constructor(private val session: Session) { +internal class TransactionalSession( + @Transient + private val session: Session, + private val login: String = session.userID, +) : UserSession { + private val creation: Date = Date() private val lock = ReentrantLock() /** @@ -77,6 +83,12 @@ internal constructor(private val session: Session) { this.lock.isHeldByCurrentThread || throw SessionOpenAndInUseException() } + override fun getUserLogin(): String = this.login + + override fun getCreationDate(): Date = this.creation + + override fun getExpirationDate(): Date = Date(Long.MAX_VALUE) + companion object { private const val TIMEOUT: Long = 60 diff --git a/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/RepositoryTest.kt b/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/RepositoryTest.kt index b89c4a4d..641ddf0a 100644 --- a/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/RepositoryTest.kt +++ b/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/RepositoryTest.kt @@ -3,10 +3,10 @@ package solutions.digamma.damas.jcr import org.junit.After import org.junit.Before import solutions.digamma.damas.cdi.ContainerTest -import solutions.digamma.damas.login.LoginManager -import solutions.digamma.damas.login.Token -import solutions.digamma.damas.session.Transaction -import solutions.digamma.damas.session.TransactionManager +import solutions.digamma.damas.session.LoginManager +import solutions.digamma.damas.session.Token +import solutions.digamma.damas.session.Connection +import solutions.digamma.damas.session.ConnectionManager /** * @author Ahmad Shahwan @@ -14,17 +14,17 @@ import solutions.digamma.damas.session.TransactionManager open class RepositoryTest : ContainerTest() { protected lateinit var login: LoginManager - protected lateinit var authenticator: TransactionManager + protected lateinit var authenticator: ConnectionManager private var token: Token? = null - private var transaction: Transaction? = null + private var connection: Connection? = null @Before @Throws(Exception::class) fun setUpWeld() { this.login = inject(LoginManager::class.java) - this.authenticator = inject(TransactionManager::class.java) + this.authenticator = inject(ConnectionManager::class.java) } @After @@ -34,15 +34,15 @@ open class RepositoryTest : ContainerTest() { protected fun login() { this.token = this.login.login("admin", "admin") - this.transaction = authenticator.begin(this.token) + this.connection = authenticator.connect(this.token) } protected fun logout() { this.token?.let { this.login.logout(it) } - this.transaction?.close() + this.connection?.close() } protected fun commit() { - this.transaction?.commit() + this.connection?.commit() } } diff --git a/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/auth/JcrPermissionManagerTest.kt b/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/auth/JcrPermissionManagerTest.kt index 92ba3fb7..5b0a115b 100644 --- a/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/auth/JcrPermissionManagerTest.kt +++ b/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/auth/JcrPermissionManagerTest.kt @@ -9,7 +9,7 @@ import solutions.digamma.damas.content.DocumentManager import solutions.digamma.damas.content.FolderManager import solutions.digamma.damas.jcr.Mocks import solutions.digamma.damas.jcr.RepositoryTest -import solutions.digamma.damas.login.Token +import solutions.digamma.damas.session.Token import solutions.digamma.damas.user.GroupManager import solutions.digamma.damas.user.UserManager import java.util.Arrays @@ -175,7 +175,7 @@ class JcrPermissionManagerTest: RepositoryTest() { } private fun use(token: Token, block: () -> R): R? { - this.authenticator.begin(token).use { + this.authenticator.connect(token).use { try { return block() } catch (_: WorkspaceException) { diff --git a/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/login/JcrLoginManagerTest.kt b/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/login/JcrLoginManagerTest.kt index 645fa302..ec369d18 100644 --- a/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/login/JcrLoginManagerTest.kt +++ b/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/login/JcrLoginManagerTest.kt @@ -38,6 +38,13 @@ class JcrLoginManagerTest : RepositoryTest() { this.manager.login(this.username, this.password) } + @Test + fun testIdentify() { + val token = this.manager.login(this.username, this.password) + val session = this.manager.identify(token) + assert(session.userLogin == this.username) + } + @Test fun testLogout() { val token = this.manager.login(this.username, this.password) diff --git a/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/session/JcrTransactionManagerTest.kt b/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/session/JcrTransactionManagerTest.kt index bcc2423b..a38f1edd 100644 --- a/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/session/JcrTransactionManagerTest.kt +++ b/solutions.digamma.damas.jcr/src/test/kotlin/solutions/digamma/damas/jcr/session/JcrTransactionManagerTest.kt @@ -25,7 +25,7 @@ class JcrTransactionManagerTest : RepositoryTest() { @Test fun beginAuthorised() { val token = this.login.login("admin", "admin") - authenticator.begin(token).use { + authenticator.connect(token).use { val rootId = this.folderManager.find("/").id val folder = folderManager.create(Mocks.folder(rootId, "test")) assert(rootId == folder.parentId) { "Folder ID mismatch" } @@ -46,7 +46,7 @@ class JcrTransactionManagerTest : RepositoryTest() { @Test fun beginUnauthorisedAfter() { val token = this.login.login("admin", "admin") - authenticator.begin(token).use { + authenticator.connect(token).use { this.folderManager.find("/").id } try { diff --git a/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/auth/AuthResource.java b/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/auth/AuthResource.java index a5ff92b1..16748346 100644 --- a/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/auth/AuthResource.java +++ b/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/auth/AuthResource.java @@ -1,8 +1,9 @@ package solutions.digamma.damas.rs.auth; +import javax.ws.rs.GET; import solutions.digamma.damas.common.WorkspaceException; -import solutions.digamma.damas.login.LoginManager; -import solutions.digamma.damas.login.Token; +import solutions.digamma.damas.session.LoginManager; +import solutions.digamma.damas.session.Token; import solutions.digamma.damas.rs.common.BaseResource; import javax.inject.Inject; @@ -12,6 +13,8 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import solutions.digamma.damas.session.UserSession; +import solutions.digamma.damas.session.UserToken; /** * Authentication REST endpoint. @@ -42,10 +45,22 @@ public AuthResource() { @POST @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Token login(Credentials cred) throws WorkspaceException { + public UserToken login(Credentials cred) throws WorkspaceException { return this.manager.login(cred.getUsername(), cred.getPassword()); } + /** + * Identify connected user. + * + * @return current user session + * @throws WorkspaceException when an error occurs + */ + @GET + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + public UserSession identify() throws WorkspaceException { + return this.manager.identify(this.getToken()); + } + /** * Disconnect user, invalidating their session. * diff --git a/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/AuthenticationInterceptor.java b/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/AuthenticationInterceptor.java index 8ee5dd3d..f4967f80 100644 --- a/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/AuthenticationInterceptor.java +++ b/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/AuthenticationInterceptor.java @@ -1,9 +1,9 @@ package solutions.digamma.damas.rs.common; import solutions.digamma.damas.common.WorkspaceException; -import solutions.digamma.damas.session.Transaction; -import solutions.digamma.damas.session.TransactionManager; -import solutions.digamma.damas.login.Token; +import solutions.digamma.damas.session.Connection; +import solutions.digamma.damas.session.ConnectionManager; +import solutions.digamma.damas.session.Token; import javax.inject.Inject; import javax.interceptor.AroundInvoke; @@ -15,10 +15,10 @@ public class AuthenticationInterceptor { @Inject - private TransactionManager manager; + private ConnectionManager manager; - private Transaction begin(Token token) throws WorkspaceException { - return this.manager.begin(token); + private Connection begin(Token token) throws WorkspaceException { + return this.manager.connect(token); } @AroundInvoke @@ -29,15 +29,19 @@ public Object invoke(InvocationContext context) throws Exception { } BaseResource resource = (BaseResource) context.getTarget(); /* Code should be compilable with Java 8 for Enunciate compatibility */ - Transaction shadowTransaction = null; - try (Transaction transaction = begin(resource.getToken())) { - shadowTransaction = transaction; + Connection shadowConnection = null; + try (Connection connection = begin(resource.getToken())) { + shadowConnection = connection; Object returned = context.proceed(); - transaction.commit(); + connection.commit(); return returned; } catch (Throwable e) { - if (shadowTransaction != null) { - shadowTransaction.rollback(); + if (shadowConnection != null) { + try { + shadowConnection.rollback(); + } catch (Exception ignore) { + /* Exception when session not yet open, ignore it */ + } } throw e; } diff --git a/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/AuthenticationToken.java b/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/AuthenticationToken.java index be364d97..0cecd72d 100644 --- a/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/AuthenticationToken.java +++ b/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/AuthenticationToken.java @@ -1,6 +1,6 @@ package solutions.digamma.damas.rs.common; -import solutions.digamma.damas.login.Token; +import solutions.digamma.damas.session.Token; import java.io.Serializable; diff --git a/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/BaseResource.java b/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/BaseResource.java index 961982c9..740e3c94 100644 --- a/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/BaseResource.java +++ b/solutions.digamma.damas.rs/src/main/java/solutions/digamma/damas/rs/common/BaseResource.java @@ -1,6 +1,6 @@ package solutions.digamma.damas.rs.common; -import solutions.digamma.damas.login.Token; +import solutions.digamma.damas.session.Token; import javax.ws.rs.HeaderParam; diff --git a/solutions.digamma.damas.rs/src/test/java/solutions/digamma/damas/rs/RestTest.java b/solutions.digamma.damas.rs/src/test/java/solutions/digamma/damas/rs/RestTest.java index 3f83e4e7..65f554e7 100644 --- a/solutions.digamma.damas.rs/src/test/java/solutions/digamma/damas/rs/RestTest.java +++ b/solutions.digamma.damas.rs/src/test/java/solutions/digamma/damas/rs/RestTest.java @@ -14,7 +14,7 @@ import solutions.digamma.damas.content.Comment; import solutions.digamma.damas.content.Document; import solutions.digamma.damas.content.Folder; -import solutions.digamma.damas.login.Token; +import solutions.digamma.damas.session.Token; import solutions.digamma.damas.rs.auth.Credentials; import solutions.digamma.damas.rs.common.AuthenticationToken; import solutions.digamma.damas.rs.content.CommentSerialization; diff --git a/solutions.digamma.damas.rs/src/test/java/solutions/digamma/damas/rs/StubProviders.java b/solutions.digamma.damas.rs/src/test/java/solutions/digamma/damas/rs/StubProviders.java index 5a9dd6ab..d5e4cc6a 100644 --- a/solutions.digamma.damas.rs/src/test/java/solutions/digamma/damas/rs/StubProviders.java +++ b/solutions.digamma.damas.rs/src/test/java/solutions/digamma/damas/rs/StubProviders.java @@ -1,5 +1,6 @@ package solutions.digamma.damas.rs; +import java.util.Date; import org.mockito.Mockito; import solutions.digamma.damas.common.WorkspaceException; import solutions.digamma.damas.content.CommentManager; @@ -7,16 +8,16 @@ import solutions.digamma.damas.content.FolderManager; import solutions.digamma.damas.entity.CrudManager; import solutions.digamma.damas.entity.Entity; -import solutions.digamma.damas.session.Transaction; -import solutions.digamma.damas.session.TransactionManager; -import solutions.digamma.damas.login.LoginManager; -import solutions.digamma.damas.login.Token; +import solutions.digamma.damas.session.Connection; +import solutions.digamma.damas.session.ConnectionManager; +import solutions.digamma.damas.session.LoginManager; import solutions.digamma.damas.rs.content.CommentSerialization; import solutions.digamma.damas.rs.content.DocumentSerialization; import solutions.digamma.damas.rs.content.FolderSerialization; import solutions.digamma.damas.rs.content.MetadataSerialization; import solutions.digamma.damas.rs.user.GroupSerialization; import solutions.digamma.damas.rs.user.UserSerialization; +import solutions.digamma.damas.session.UserToken; import solutions.digamma.damas.user.GroupManager; import solutions.digamma.damas.user.UserManager; @@ -67,13 +68,13 @@ public LoginManager getLoginManager() throws WorkspaceException { } @Produces @Singleton - public TransactionManager getAuthenticationManager() + public ConnectionManager getAuthenticationManager() throws WorkspaceException { this.log.info("Acquiring mock authentication manager."); - TransactionManager manager = Mockito.mock(TransactionManager.class); - Transaction transaction = Mockito.mock(Transaction.class); - Mockito.when(manager.begin(Mockito.any())) - .thenReturn(transaction); + ConnectionManager manager = Mockito.mock(ConnectionManager.class); + Connection connection = Mockito.mock(Connection.class); + Mockito.when(manager.connect(Mockito.any())) + .thenReturn(connection); return manager; } @@ -116,11 +117,26 @@ private > M mock( return manager; } - public static class MockToken implements Token { + public static class MockToken implements UserToken { @Override public String getSecret() { return TOKEN; } + + @Override + public String getUserLogin() { + return null; + } + + @Override + public Date getCreationDate() { + return null; + } + + @Override + public Date getExpirationDate() { + return null; + } } } diff --git a/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/AuthTest.java b/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/AuthTest.java new file mode 100644 index 00000000..05dc079d --- /dev/null +++ b/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/AuthTest.java @@ -0,0 +1,79 @@ +package solutions.digamma.damas.integration; + +import java.util.HashMap; +import java.util.Map; +import javax.inject.Singleton; +import org.junit.Test; +import org.junit.runner.RunWith; +import solutions.digamma.damas.cdi.ContainerRunner; + +@RunWith(ContainerRunner.class) +@Singleton +public class AuthTest extends IntegrationTest { + + @Test + public void testGetAuth() { + Map answer = target + .path("auth") + .request(MEDIA_TYPE) + .header(AUTH_HEADER, this.getAuthHeaderValue()) + .get() + .readEntity(Map.class); + assert "admin".equals(answer.get("userLogin")) : + String.format("Wrong userLogin %s", answer.get("userLogin")); + } + + @Test + public void testGetAuthNewUser() { + Map answer; + Map body = new HashMap<>(); + String login = "jsmith"; + String password = "P@a55w0rd"; + body.put("login", login); + body.put("password", password); + answer = target + .path("users") + .request(MEDIA_TYPE) + .header(AUTH_HEADER, this.getAuthHeaderValue()) + .post(entity(body)) + .readEntity(Map.class); + String id = (String) answer.get("id"); + body.clear(); + body.put("username", login); + body.put("password", password); + Map auth = target + .path("auth") + .request(MEDIA_TYPE) + .post(entity(body)) + .readEntity(Map.class); + assert login.equals(auth.get("userLogin")) : String.format( + "Wrong userLogin in UserToken %s", + answer.get("userLogin") + ); + String token = (String) auth.get("secret"); + answer = target + .path("auth") + .request(MEDIA_TYPE) + .header(AUTH_HEADER, this.getAuthHeaderValue(token)) + .get() + .readEntity(Map.class); + assert login.equals(answer.get("userLogin")) : String.format( + "Wrong userLogin in UserSession %s", + answer.get("userLogin") + ); + + /* Logout user */ + target + .path("auth") + .request(MEDIA_TYPE) + .header(AUTH_HEADER, token) + .delete(); + + target + .path("users") + .path(id) + .request(MEDIA_TYPE) + .header(AUTH_HEADER, this.getAuthHeaderValue()) + .delete(); + } +} diff --git a/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/IntegrationTest.java b/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/IntegrationTest.java index 577ba508..4efd28b1 100644 --- a/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/IntegrationTest.java +++ b/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/IntegrationTest.java @@ -40,8 +40,12 @@ public abstract class IntegrationTest { private Launcher launcher; protected String getAuthHeaderValue() { - assert this.token != null; - return String.format("%s %s", AUTH_SCHEME, this.token); + return this.getAuthHeaderValue(this.token); + } + + protected String getAuthHeaderValue(String token) { + assert token != null; + return String.format("%s %s", AUTH_SCHEME, token); } @PostConstruct diff --git a/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/UserTest.java b/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/UserTest.java index aae7b6b2..05f9e826 100644 --- a/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/UserTest.java +++ b/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/integration/UserTest.java @@ -24,7 +24,7 @@ public class UserTest extends IntegrationTest { @Test public void testCreateRetrieveDeleteUser() { - Map answer; + Map answer; Map body = new HashMap<>(); String firstName = "Jonh"; String lastName = "Smith"; diff --git a/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/jcr/ContentTest.java b/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/jcr/ContentTest.java index 57b5ee25..3916504b 100644 --- a/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/jcr/ContentTest.java +++ b/solutions.digamma.damas.standalone/src/test/java/solutions/digamma/damas/jcr/ContentTest.java @@ -5,10 +5,10 @@ import org.mockito.Mockito; import solutions.digamma.damas.common.WorkspaceException; import solutions.digamma.damas.common.NotFoundException; -import solutions.digamma.damas.session.Transaction; -import solutions.digamma.damas.session.TransactionManager; -import solutions.digamma.damas.login.LoginManager; -import solutions.digamma.damas.login.Token; +import solutions.digamma.damas.session.Connection; +import solutions.digamma.damas.session.ConnectionManager; +import solutions.digamma.damas.session.LoginManager; +import solutions.digamma.damas.session.Token; import solutions.digamma.damas.cdi.ContainerRunner; import solutions.digamma.damas.content.Document; import solutions.digamma.damas.content.DocumentManager; @@ -29,7 +29,7 @@ public class ContentTest { private LoginManager loginMgr; @Inject - private TransactionManager transactionMgr; + private ConnectionManager transactionMgr; @Inject private DocumentManager documentMgr; @@ -53,8 +53,8 @@ private Document createDocument(String parentId, String name) public void testContent() throws WorkspaceException { Token adminToken = loginMgr.login("admin", "admin"); assert adminToken != null; - try (Transaction transaction = transactionMgr.begin(adminToken)) { - assert transaction != null; + try (Connection connection = transactionMgr.connect(adminToken)) { + assert connection != null; Folder rootFolder = this.folderMgr.find("/"); final String testFileName = "test.txt"; Document doc;