/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.service.simple;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import divconq.bus.IService;
import divconq.bus.Message;
import divconq.hub.Hub;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationContextBuilder;
import divconq.lang.op.UserContext;
import divconq.mod.ExtensionBase;
import divconq.session.Session;
import divconq.struct.FieldStruct;
import divconq.struct.RecordStruct;
import divconq.util.HexUtil;
import divconq.util.StringUtil;
import divconq.work.TaskRun;
import divconq.xml.XElement;
public class AuthService extends ExtensionBase implements IService {
protected Set<String> sessions = new HashSet<String>();
protected SecureRandom random = new SecureRandom();
protected String authpass = null;
protected boolean xmlMode = false;
protected Map<String, DomainUsers> xmlData = null;
@Override
public void init(XElement config) {
super.init(config);
if (config != null)
this.authpass = config.getAttribute("Password");
XElement mdomains = Hub.instance.getConfig().selectFirst("Domains");
if (mdomains != null) {
this.xmlData = new HashMap<>();
this.xmlMode = true;
for (XElement mdomain : mdomains.selectAll("Domain")) {
String id = mdomain.getAttribute("Id");
DomainUsers du = new DomainUsers();
du.load(id, mdomain);
this.xmlData.put(id, du);
}
}
}
@Override
public String serviceName() {
// TODO if through Loader then get name there (super)
return "dcAuth";
}
@Override
public void handle(TaskRun request) {
Message msg = (Message) request.getTask().getParams();
String feature = msg.getFieldAsString("Feature");
String op = msg.getFieldAsString("Op");
UserContext uc = request.getContext().getUserContext();
//System.out.println("Auth: " + feature + " - " + op);
if ("Authentication".equals(feature)) {
if ("SignIn".equals(op)) {
String uname = uc.getUsername();
if (this.xmlMode) {
DomainUsers du = this.xmlData.get(uc.getDomainId());
if (du == null) {
this.clearUserContext(request.getContext());
request.error("Domain not found");
}
else {
RecordStruct urec = du.info(uname);
if (urec == null) {
this.clearUserContext(request.getContext());
request.error("User not found");
}
else {
request.setResult(urec);
}
}
}
else {
request.setResult(new RecordStruct(
new FieldStruct("Username", "root"),
new FieldStruct("FirstName", "Root"),
new FieldStruct("LastName", "User"),
new FieldStruct("Email", "root@locahost")
));
}
request.complete();
return;
}
if ("Verify".equals(op)) {
String authToken = uc.getAuthToken();
if (StringUtil.isNotEmpty(authToken)) {
//System.out.println("---------- Token not empty");
if (this.xmlMode) {
Session sess = Hub.instance.getSessions().lookup(request.getContext().getSessionId());
//System.out.println("---------- Xml Mode");
if ((sess != null) && authToken.equals(sess.getUser().getAuthToken())) {
//System.out.println("---------- Token verified");
// verified
request.complete();
return;
}
}
else if (this.sessions.contains(authToken)) {
//System.out.println("verify existing");
request.complete();
return;
}
}
//System.out.println("---------- Token empty or bad");
// else try to authenticate
RecordStruct creds = uc.getCredentials(); // msg.getFieldAsRecord("Credentials");
if (creds == null) {
this.clearUserContext(request.getContext());
request.errorTr(442);
request.complete();
return;
}
//System.out.println("---------- Using Creds");
String uname = creds.getFieldAsString("Username");
String passwd = creds.getFieldAsString("Password");
if (this.xmlMode) {
//System.out.println("---------- Xml Mode");
DomainUsers du = this.xmlData.get(uc.getDomainId());
if ((du == null) || !du.verify(uname, passwd)) {
this.clearUserContext(request.getContext());
request.errorTr(442);
request.complete();
return;
}
}
else if (StringUtil.isNotEmpty(this.authpass)) {
passwd = Hub.instance.getClock().getObfuscator().hashStringToHex(passwd);
if (!"root".equals(uname) || !this.authpass.equals(passwd)) {
this.clearUserContext(request.getContext());
request.errorTr(442);
request.complete();
return;
}
}
else {
if (!"root".equals(uname) || !"A1s2d3f4".equals(passwd)) {
this.clearUserContext(request.getContext());
request.errorTr(442);
request.complete();
return;
}
}
byte[] feedbuff = new byte[32];
this.random.nextBytes(feedbuff);
String token = HexUtil.bufferToHex(feedbuff);
//System.out.println("---------- Verified and token");
// create the new context
if (this.xmlMode)
uc = this.xmlData.get(uc.getDomainId()).context(uname, token);
else
uc = request.getContext().toBuilder().elevateToRootTask().withAuthToken(token).toUserContext();
// make sure we use the new context in our return
OperationContext.switchUser(request.getContext(), uc);
if (this.xmlMode) {
//Session ss =
Hub.instance.getSessions().findOrCreateTether(request.getContext());
//System.out.println("---------- Session added: " + ss);
}
else {
this.sessions.add(token);
}
//System.out.println("verify new");
request.complete();
return;
}
if ("SignOut".equals(op)) {
String authToken = uc.getAuthToken();
if (this.xmlMode) {
Hub.instance.getSessions().terminate(request.getContext().getSessionId());
//System.out.println("---------- Session removed");
}
else if (StringUtil.isNotEmpty(authToken))
this.sessions.remove(authToken);
this.clearUserContext(request.getContext());
request.complete();
return;
}
}
else if ("Recovery".equals(feature)) {
if ("Initiate".equals(op)) {
request.complete();
return;
}
}
request.errorTr(441, this.serviceName(), feature, op);
request.complete();
}
// be sure we keep the domain id
public void clearUserContext(OperationContext ctx) {
UserContext uc = ctx.getUserContext();
OperationContext.switchUser(ctx, new OperationContextBuilder()
.withGuestUserTemplate()
.withDomainId(uc.getDomainId())
.toUserContext());
}
public class DomainUsers {
protected String did = null;
protected Map<String, XElement> cachedElement = new HashMap<>();
protected Map<String, XElement> cachedIndex = new HashMap<>();
protected Map<String, RecordStruct> cachedUserRecord = new HashMap<>();
protected Map<String, OperationContextBuilder> cachedUserContext = new HashMap<>();
public boolean verify(String username, String password) {
XElement usr = this.cachedIndex.get(username);
if (usr == null)
return false;
String upass = usr.getAttribute("Password");
// any setting in the config file is set with Hub crypto not domain crypto
String passwd = Hub.instance.getClock().getObfuscator().hashStringToHex(password);
// if they are the same length then xml file must have hashed value too, use it
if ((passwd != null) && (passwd.length() == upass.length()))
return passwd.equals(upass);
// if not same length then xml file is probably plain text, use that
return password.equals(upass);
}
public UserContext context(String username, String token) {
XElement usr = this.cachedIndex.get(username);
if ((usr == null))
return null;
String uid = usr.getAttribute("Id");
return this.cachedUserContext.get(uid).withAuthToken(token).toUserContext();
}
public RecordStruct info(String username) {
XElement usr = this.cachedIndex.get(username);
if (usr == null)
return null;
String uid = usr.getAttribute("Id");
return this.cachedUserRecord.get(uid);
}
public void load(String did, XElement domain) {
this.did = did;
for (XElement usr : domain.selectAll("User")) {
String uid = usr.getAttribute("Id");
this.cachedElement.put(uid, usr);
this.cachedIndex.put(usr.getAttribute("Username"), usr);
this.cachedUserRecord.put(uid, new RecordStruct(
new FieldStruct("Username", usr.getAttribute("Username")),
new FieldStruct("FirstName", usr.getAttribute("First")),
new FieldStruct("LastName", usr.getAttribute("Last")),
new FieldStruct("Email", usr.getAttribute("Email"))
));
List<XElement> tags = usr.selectAll("AuthTag");
String[] atags = new String[tags.size() + 1];
atags[0] = "User";
for (int i = 1; i < atags.length; i++)
atags[i] = tags.get(i - 1).getText();
String fullname = "";
if (usr.hasAttribute("First"))
fullname = usr.getAttribute("First");
if (usr.hasAttribute("Last") && StringUtil.isNotEmpty(fullname))
fullname += " " + usr.getAttribute("Last");
else if (usr.hasAttribute("Last"))
fullname = usr.getAttribute("Last");
if (StringUtil.isEmpty(fullname))
fullname = "[unknown]";
this.cachedUserContext.put(uid, new OperationContextBuilder()
.withDomainId(did)
.withUserId(uid)
.withUsername(usr.getAttribute("Username"))
.withFullName(fullname)
.withEmail(usr.getAttribute("Email"))
.withVerified(true)
.withAuthTags(atags)
);
}
}
}
}