/* Copyright 2014 Danish Maritime Authority.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.maritimecloud.portal.resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import net.maritimecloud.common.resource.AbstractCommandResource;
import net.maritimecloud.identityregistry.command.api.SendResetPasswordInstructions;
import net.maritimecloud.identityregistry.domain.Identity;
import net.maritimecloud.identityregistry.domain.IdentityService;
import net.maritimecloud.identityregistry.domain.Role;
import net.maritimecloud.portal.application.ApplicationServiceRegistry;
import net.maritimecloud.portal.security.AuthenticationException;
import net.maritimecloud.portal.security.AuthenticationUtil;
import net.maritimecloud.portal.security.UserNotLoggedInException;
import org.apache.shiro.authc.UnknownAccountException;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service resource for authenticating a user and getting associated roles etc...
* <p>
* @author Christoffer Børrild
*/
@Path("/authentication")
public class AuthenticationResource extends AbstractCommandResource {
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationResource.class);
@Override
protected CommandGateway commandGateway() {
return ApplicationServiceRegistry.commandGateway();
}
protected AuthenticationUtil authenticationUtil() {
return ApplicationServiceRegistry.authenticationUtil();
}
private IdentityService identityService() {
return ApplicationServiceRegistry.identityService();
}
private LogService logService() {
return ApplicationServiceRegistry.logService();
}
@POST
@Path("/login")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public SubjectDTO login(CredentialsDTO credentials) {
assertCredentialsNotNull(credentials);
return tryLogin(credentials);
}
private SubjectDTO tryLogin(CredentialsDTO credentials) throws UserNotAuthenticated {
try {
return doLogin(credentials);
} catch (AuthenticationException e) {
reportWrongUsernamePassword(credentials);
throw new UserNotAuthenticated();
}
}
private SubjectDTO doLogin(CredentialsDTO credentials) throws AuthenticationException {
authenticationUtil().login(credentials.username, credentials.password);
reportUserLoggedIn(credentials.username);
return currentSubject();
}
private void assertCredentialsNotNull(CredentialsDTO credentials) throws IllegalArgumentException {
if (credentials == null) {
throw new IllegalArgumentException();
}
if (credentials.getUsername() == null) {
throw new IllegalArgumentException();
}
}
@POST
@Path("/logout")
public void logout() {
logService().reportUserLoggingOut();
authenticationUtil().logout();
}
@POST // TODO: should be a PUT !?!?
@Consumes(APPLICATION_JSON_CQRS_COMMAND)
@Path("")
public void sendForgotPutCommand(
@HeaderParam("Content-type") String contentType,
@QueryParam("command") @DefaultValue("") String queryCommandName,
String commandJSON
) {
LOG.info("AuthenticationResource PUT command");
sendAndWait(contentType, queryCommandName, commandJSON,
SendResetPasswordInstructions.class
);
}
private void reportWrongUsernamePassword(CredentialsDTO credentials) {
LOG.debug("User {} not logged in (wrong username / password)", credentials.username);
logService().reportWrongUsernamePassword(credentials.username);
}
private void reportUserLoggedIn(String username) {
LOG.debug("User {} logged in", username);
logService().reportUserLoggedIn(username);
}
private void reportCurrentSubjectNotAuthenticated(java.lang.Exception e) {
LOG.debug("Current user is not authenticated: ", e);
logService().reportDebugError("Current user is not authenticated: ", e);
}
@GET
@Path("/sink.html")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String sinkSubmitsFromPasswordManagers() {
// dummy sink to consume "form submits" from browser password managers
return "";
}
@POST
@Path("/sink.html")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String sinkSubmitsFromPasswordManagersPost() {
// dummy sink to consume "form submits" from browser password managers
return "";
}
@GET
@Path("/currentsubject")
@Produces(MediaType.APPLICATION_JSON)
public SubjectDTO currentSubject() {
try {
String userIdentifier = authenticationUtil().getUserId();
// Lookup user
Identity identity = identityService().findByUserId(userIdentifier);
assertUserFound(identity);
return createSubject(identity);
} catch (UserNotLoggedInException | UnknownAccountException e) {
reportCurrentSubjectNotAuthenticated(e);
throw new UserNotAuthenticated();
}
}
private void assertUserFound(Identity identity) throws UnknownAccountException {
if (identity == null) {
throw new UnknownAccountException("No user found in application registry");
}
}
private SubjectDTO createSubject(Identity identity) {
return new SubjectDTO(identity.username(), extractRolenamesAsArrayOfStrings(identity));
}
private String[] extractRolenamesAsArrayOfStrings(Identity identity) {
return identity.username().equalsIgnoreCase("admin")
? new String[]{Role.USER.name(), Role.ADMIN.name()}
: new String[]{Role.USER.name()};
}
/**
* This is the representation of the logged in user as sent back to the clients
*/
public static class SubjectDTO {
private String username;
private String[] roles;
public SubjectDTO() {
}
public SubjectDTO(String username, String[] roles) {
this.username = username;
this.roles = roles;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String[] getRoles() {
return roles;
}
public void setRoles(String[] roles) {
this.roles = roles;
}
}
public static class CredentialsDTO {
String username;
String password;
String emailAddress;
String verificationId;
public CredentialsDTO() {
}
public CredentialsDTO(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getVerificationId() {
return verificationId;
}
public void setVerificationId(String verificationId) {
this.verificationId = verificationId;
}
@Override
public String toString() {
return "CredentialsDTO{" + "username=" + username + ", password=" + password + '}';
}
}
public static class UserNotAuthenticated extends WebApplicationException {
public UserNotAuthenticated() {
super(Response.Status.UNAUTHORIZED);
}
}
}