package com.evolveum.midpoint.model.impl.security;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import com.evolveum.midpoint.model.api.AuthenticationEvaluator;
import com.evolveum.midpoint.model.api.ModelInteractionService;
import com.evolveum.midpoint.model.api.context.SecurityQuestionsAuthenticationContext;
import com.evolveum.midpoint.model.impl.util.RestServiceUtil;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.schema.SearchResultList;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectQueryUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.Producer;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SecurityPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SecurityQuestionAnswerType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SecurityQuestionDefinitionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.MissingNode;
@Component
public class MidpointRestSecurityQuestionsAuthenticator extends MidpointRestAuthenticator<SecurityQuestionsAuthenticationContext> {
protected static final String USER_CHALLENGE = "\"user\" : \"username\"";
protected static final String USER_QUESTION_ANSWER_CHALLENGE = ", \"answer\" :";
protected static final String QUESTION = "{\"qid\" : \"$QID\", \"qtxt\" : \"$QTXT\"}";
private static final String Q_ID = "$QID";
private static final String Q_TXT = "$QTXT";
@Autowired
private PrismContext prismContext;
@Autowired
private ModelInteractionService modelInteractionService;
@Autowired(required = true)
private AuthenticationEvaluator<SecurityQuestionsAuthenticationContext> securityQuestionsAuthenticationEvaluator;
@Override
protected AuthenticationEvaluator<SecurityQuestionsAuthenticationContext> getAuthenticationEvaluator() {
return securityQuestionsAuthenticationEvaluator;
}
@Override
protected SecurityQuestionsAuthenticationContext createAuthenticationContext(AuthorizationPolicy policy, ContainerRequestContext requestCtx) {
JsonFactory f = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(f);
JsonNode node = null;
try {
node = mapper.readTree(policy.getAuthorization());
} catch (IOException e) {
RestServiceUtil.createSecurityQuestionAbortMessage(requestCtx, "{" + USER_CHALLENGE + "}");
return null;
}
JsonNode userNameNode = node.findPath("user");
if (userNameNode instanceof MissingNode) {
RestServiceUtil.createSecurityQuestionAbortMessage(requestCtx, "{" + USER_CHALLENGE + "}");
return null;
}
String userName = userNameNode.asText();
policy.setUserName(userName);
JsonNode answerNode = node.findPath("answer");
if (answerNode instanceof MissingNode) {
SecurityContextHolder.getContext().setAuthentication(new AnonymousAuthenticationToken("restapi", "REST", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")));
SearchResultList<PrismObject<UserType>> users = null;
try {
users = searchUser(userName);
} finally {
SecurityContextHolder.getContext().setAuthentication(null);
}
if (users.size() != 1) {
requestCtx.abortWith(Response.status(Status.UNAUTHORIZED).header("WWW-Authenticate", "Security question authentication failed. Incorrect username and/or password").build());
return null;
}
PrismObject<UserType> user = users.get(0);
PrismContainer<SecurityQuestionAnswerType> questionAnswerContainer = user.findContainer(SchemaConstants.PATH_SECURITY_QUESTIONS_QUESTION_ANSWER);
if (questionAnswerContainer == null || questionAnswerContainer.isEmpty()){
requestCtx.abortWith(Response.status(Status.UNAUTHORIZED).header("WWW-Authenticate", "Security question authentication failed. Incorrect username and/or password").build());
return null;
}
String questionChallenge = "";
List<SecurityQuestionDefinitionType> questions = null;
try {
SecurityContextHolder.getContext().setAuthentication(new AnonymousAuthenticationToken("restapi", "REST", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")));
questions = getQuestions(user);
} finally {
SecurityContextHolder.getContext().setAuthentication(null);
}
Collection<SecurityQuestionAnswerType> questionAnswers = questionAnswerContainer.getRealValues();
Iterator<SecurityQuestionAnswerType> questionAnswerIterator = questionAnswers.iterator();
while (questionAnswerIterator.hasNext()) {
SecurityQuestionAnswerType questionAnswer = questionAnswerIterator.next();
SecurityQuestionDefinitionType question = questions.stream().filter(q -> q.getIdentifier().equals(questionAnswer.getQuestionIdentifier())).findFirst().get();
String challenge = QUESTION.replace(Q_ID, question.getIdentifier());
questionChallenge += challenge.replace(Q_TXT, question.getQuestionText());
if (questionAnswerIterator.hasNext()) {
questionChallenge += ",";
}
}
String userChallenge = USER_CHALLENGE.replace("username", userName);
String challenge = "{" + userChallenge + ", \"answer\" : [" + questionChallenge + "]}";
RestServiceUtil.createSecurityQuestionAbortMessage(requestCtx, challenge);
return null;
}
ArrayNode answers = (ArrayNode) answerNode;
Iterator<JsonNode> answersList = answers.elements();
Map<String, String> questionAnswers = new HashMap<>();
while (answersList.hasNext()) {
JsonNode answer = answersList.next();
String questionId = answer.findPath("qid").asText();
String questionAnswer = answer.findPath("qans").asText();
questionAnswers.put(questionId, questionAnswer);
}
return new SecurityQuestionsAuthenticationContext(userName, questionAnswers);
}
private SearchResultList<PrismObject<UserType>> searchUser(String userName) {
return getSecurityEnforcer().runPrivileged(new Producer<SearchResultList<PrismObject<UserType>>>() {
@Override
public SearchResultList<PrismObject<UserType>> run() {
Task task = getTaskManager().createTaskInstance("Search user by name");
OperationResult result = task.getResult();
SearchResultList<PrismObject<UserType>> users;
try {
users = getModel().searchObjects(UserType.class, ObjectQueryUtil.createNameQuery(userName, prismContext), null, task, result);
} catch (SchemaException | ObjectNotFoundException | SecurityViolationException
| CommunicationException | ConfigurationException e) {
return null;
} finally {
SecurityContextHolder.getContext().setAuthentication(null);
}
return users;
}
});
}
private List<SecurityQuestionDefinitionType> getQuestions(PrismObject<UserType> user) {
return getSecurityEnforcer().runPrivileged(new Producer<List<SecurityQuestionDefinitionType>>() {
@Override
public List<SecurityQuestionDefinitionType> run() {
Task task = getTaskManager().createTaskInstance("Search user by name");
OperationResult result = task.getResult();
SecurityPolicyType securityPolicyType = null;
try {
SecurityContextHolder.getContext().setAuthentication(new AnonymousAuthenticationToken("rest_sec_q_auth", "REST", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")));
securityPolicyType = modelInteractionService.getSecurityPolicy(user, task, result);
} catch (ObjectNotFoundException | SchemaException e) {
return null;
} finally {
SecurityContextHolder.getContext().setAuthentication(null);
}
if (securityPolicyType.getCredentials() != null && securityPolicyType.getCredentials().getSecurityQuestions() != null){
return securityPolicyType.getCredentials().getSecurityQuestions().getQuestion();
}
return null;
}
});
}
}