- private class MayCreateCred implements MayChange {
- private Result<NsDAO.Data> nsd;
- private AuthzTrans trans;
- private CredDAO.Data cred;
- private Executor exec;
-
- public MayCreateCred(AuthzTrans trans, CredDAO.Data cred, Executor exec) {
- this.trans = trans;
- this.cred = cred;
- this.exec = exec;
- }
-
- @Override
- public Result<?> mayChange() {
- if(nsd==null) {
- nsd = ques.validNSOfDomain(trans, cred.id);
- }
- // is Ns of CredID valid?
- if(nsd.isOK()) {
- try {
- // Check Org Policy
- if(trans.org().validate(trans,Policy.CREATE_MECHID, exec, cred.id)==null) {
- return Result.ok();
- } else {
- Result<?> rmc = ques.mayUser(trans, trans.user(), nsd.value, Access.write);
- if(rmc.isOKhasData()) {
- return rmc;
- }
- }
- } catch (Exception e) {
- trans.warn().log(e);
- }
- } else {
- trans.warn().log(nsd.errorString());
- }
- return Result.err(Status.ERR_Denied,"%s is not allowed to create %s in %s",trans.user(),cred.id,cred.ns);
- }
- }
-
- private class MayChangeCred implements MayChange {
-
- private Result<NsDAO.Data> nsd;
- private AuthzTrans trans;
- private CredDAO.Data cred;
- public MayChangeCred(AuthzTrans trans, CredDAO.Data cred) {
- this.trans = trans;
- this.cred = cred;
- }
-
- @Override
- public Result<?> mayChange() {
- // User can change himself (but not create)
- if(trans.user().equals(cred.id)) {
- return Result.ok();
- }
- if(nsd==null) {
- nsd = ques.validNSOfDomain(trans, cred.id);
- }
- // Get the Namespace
- if(nsd.isOK()) {
- if(ques.mayUser(trans, trans.user(), nsd.value,Access.write).isOK()) {
- return Result.ok();
- }
- String user[] = Split.split('.',trans.user());
- if(user.length>2) {
- String company = user[user.length-1] + '.' + user[user.length-2];
- if(ques.isGranted(trans, trans.user(), ROOT_NS,"password",company,"reset")) {
- return Result.ok();
- }
- }
- }
- return Result.err(Status.ERR_Denied,"%s is not allowed to change %s in %s",trans.user(),cred.id,cred.ns);
- }
-
- }
-
- private final long DAY_IN_MILLIS = 24*3600*1000L;
-
- @ApiDoc(
- method = POST,
- path = "/authn/cred",
- params = {},
- expectedCode = 201,
- errorCodes = {403,404,406,409},
- text = { "A credential consists of:",
- "<ul><li>id - the ID to create within AAF. The domain is in reverse",
- "order of Namespace (i.e. Users of Namespace com.att.myapp would be",
- "AB1234@myapp.att.com</li>",
- "<li>password - Company Policy Compliant Password</li></ul>",
- "Note: AAF does support multiple credentials with the same ID.",
- "Check with your organization if you have this implemented."
- }
- )
- @Override
- public Result<Void> createUserCred(final AuthzTrans trans, REQUEST from) {
- final String cmdDescription = ("Create User Credential");
- TimeTaken tt = trans.start(cmdDescription, Env.SUB);
-
- try {
- Result<CredDAO.Data> rcred = mapper.cred(trans, from, true);
- if(rcred.isOKhasData()) {
- byte[] rawCred = rcred.value.cred.array();
- rcred = ques.userCredSetup(trans, rcred.value);
-
- final ServiceValidator v = new ServiceValidator();
-
- if(v.cred(trans, trans.org(),rcred,true).err()) { // Note: Creates have stricter Validations
- return Result.err(Status.ERR_BadData,v.errs());
- }
-
-
- // 2016-4 Jonathan, New Behavior - If MechID is not registered with Org, deny creation
- Identity mechID = null;
- Organization org = trans.org();
- try {
- mechID = org.getIdentity(trans, rcred.value.id);
- } catch (Exception e1) {
- trans.error().log(e1,rcred.value.id,"cannot be validated at this time");
- }
- if(mechID==null || !mechID.isFound()) {
- return Result.err(Status.ERR_Policy,"MechIDs must be registered with %s before provisioning in AAF",org.getName());
- }
-
- Result<List<NsDAO.Data>> nsr = ques.nsDAO.read(trans, rcred.value.ns);
- if(nsr.notOKorIsEmpty()) {
- return Result.err(Status.ERR_NsNotFound,"Cannot provision %s on non-existent Namespace %s",mechID.id(),rcred.value.ns);
- }
-
-
- boolean firstID = false;
- MayChange mc;
-
- CassExecutor exec = new CassExecutor(trans, func);
- Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, rcred.value.id);
- if (rlcd.isOKhasData()) {
- if (!org.canHaveMultipleCreds(rcred.value.id)) {
- return Result.err(Status.ERR_ConflictAlreadyExists, "Credential exists");
- }
- Result<Boolean> rb;
- for (CredDAO.Data curr : rlcd.value) {
- // May not use the same password in the list
- // Note: ASPR specifies character differences, but we don't actually store the
- // password to validate char differences.
-
- rb = ques.userCredCheck(trans, curr, rawCred);
- if(rb.notOK()) {
- return Result.err(rb);
- } else if(rb.value){
- return Result.err(Status.ERR_Policy, "Credential content cannot be reused.");
- } else if (Chrono.dateOnlyStamp(curr.expires).equals(Chrono.dateOnlyStamp(rcred.value.expires)) && curr.type==rcred.value.type) {
- return Result.err(Status.ERR_ConflictAlreadyExists, "Credential with same Expiration Date exists, use 'reset'");
- }
- }
- } else {
- try {
- // 2016-04-12 Jonathan If Caller is the Sponsor and is also an Owner of NS, allow without special Perm
- String theMechID = rcred.value.id;
- Boolean otherMechIDs = false;
- // find out if this is the only mechID. other MechIDs mean special handling (not automated)
- for(CredDAO.Data cd : ques.credDAO.readNS(trans,nsr.value.get(0).name).value) {
- if(!cd.id.equals(theMechID)) {
- otherMechIDs = true;
- break;
- }
- }
- String reason;
- // We can say "ID does not exist" here
- if((reason=org.validate(trans, Policy.CREATE_MECHID, exec, theMechID,trans.user(),otherMechIDs.toString()))!=null) {
- return Result.err(Status.ERR_Denied, reason);
- }
- firstID=true;
- } catch (Exception e) {
- return Result.err(e);
- }
- }
-
- mc = new MayCreateCred(trans, rcred.value, exec);
-
- final CredDAO.Data cdd = rcred.value;
- Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from, rcred.value,false, // may want to enable in future.
- new Mapper.Memo() {
- @Override
- public String get() {
- return cmdDescription + " [" +
- cdd.id + '|'
- + cdd.type + '|'
- + cdd.expires + ']';
- }
- },
- mc);
-
- switch(fd.status) {
- case OK:
- Result<String> rfc = func.createFuture(trans, fd.value,
- rcred.value.id + '|' + rcred.value.type.toString() + '|' + rcred.value.expires,
- trans.user(), nsr.value.get(0), FUTURE_OP.C);
- if(rfc.isOK()) {
- return Result.err(Status.ACC_Future, "Credential Request [%s|%s|%s] is saved for future processing",
- rcred.value.id,
- Integer.toString(rcred.value.type),
- rcred.value.expires.toString());
- } else {
- return Result.err(rfc);
- }
- case Status.ACC_Now:
- try {
- if(firstID) {
- // && !nsr.value.get(0).isAdmin(trans.getUserPrincipal().getName())) {
- Result<List<String>> admins = func.getAdmins(trans, nsr.value.get(0).name, false);
- // OK, it's a first ID, and not by NS Admin, so let's set TempPassword length
- // Note, we only do this on First time, because of possibility of
- // prematurely expiring a production id
- if(admins.isOKhasData() && !admins.value.contains(trans.user())) {
- rcred.value.expires = org.expiration(null, Expiration.TempPassword).getTime();
- }
- }
- } catch (Exception e) {
- trans.error().log(e, "While setting expiration to TempPassword");
- }
- Result<?>udr = ques.credDAO.create(trans, rcred.value);
- if(udr.isOK()) {
- return Result.ok();
- }
- return Result.err(udr);
- default:
- return Result.err(fd);
- }
-
- } else {
- return Result.err(rcred);
- }
- } finally {
- tt.done();
- }
- }
-
- @ApiDoc(
- method = GET,
- path = "/authn/creds/ns/:ns",
- params = {"ns|string|true"},
- expectedCode = 200,
- errorCodes = {403,404,406},
- text = { "Return all IDs in Namespace :ns"
- }
- )
- @Override
- public Result<USERS> getCredsByNS(AuthzTrans trans, String ns) {
- final Validator v = new ServiceValidator();
- if(v.ns(ns).err()) {
- return Result.err(Status.ERR_BadData,v.errs());
- }
-
- // check if user is allowed to view NS
- Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
- if(rnd.notOK()) {
- return Result.err(rnd);
- }
- rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
- if(rnd.notOK()) {
- return Result.err(rnd);
- }
-
- TimeTaken tt = trans.start("MAP Creds by NS to Creds", Env.SUB);
- try {
- USERS users = mapper.newInstance(API.USERS);
- Result<List<CredDAO.Data>> rlcd = ques.credDAO.readNS(trans, ns);
-
- if(rlcd.isOK()) {
- if(!rlcd.isEmpty()) {
- return mapper.cred(rlcd.value, users);
- }
- return Result.ok(users);
- } else {
- return Result.err(rlcd);
- }
- } finally {
- tt.done();
- }
-
- }
-
- @ApiDoc(
- method = GET,
- path = "/authn/creds/id/:ns",
- params = {"id|string|true"},
- expectedCode = 200,
- errorCodes = {403,404,406},
- text = { "Return all IDs in for ID"
- ,"(because IDs are multiple, due to multiple Expiration Dates)"
- }
- )
- @Override
- public Result<USERS> getCredsByID(AuthzTrans trans, String id) {
- final Validator v = new ServiceValidator();
- if(v.nullOrBlank("ID",id).err()) {
- return Result.err(Status.ERR_BadData,v.errs());
- }
-
- String ns = Question.domain2ns(id);
- // check if user is allowed to view NS
- Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
- if(rnd.notOK()) {
- return Result.err(rnd);
- }
- rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
- if(rnd.notOK()) {
- return Result.err(rnd);
- }
-
- TimeTaken tt = trans.start("MAP Creds by ID to Creds", Env.SUB);
- try {
- USERS users = mapper.newInstance(API.USERS);
- Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, id);
-
- if(rlcd.isOK()) {
- if(!rlcd.isEmpty()) {
- return mapper.cred(rlcd.value, users);
- }
- return Result.ok(users);
- } else {
- return Result.err(rlcd);
- }
- } finally {
- tt.done();
- }
-
- }
-
- @ApiDoc(
- method = GET,
- path = "/authn/certs/id/:id",
- params = {"id|string|true"},
- expectedCode = 200,
- errorCodes = {403,404,406},
- text = { "Return Cert Info for ID"
- }
- )
- @Override
- public Result<CERTS> getCertInfoByID(AuthzTrans trans, HttpServletRequest req, String id) {
- TimeTaken tt = trans.start("Get Cert Info by ID", Env.SUB);
- try {
- CERTS certs = mapper.newInstance(API.CERTS);
- Result<List<CertDAO.Data>> rlcd = ques.certDAO.readID(trans, id);
-
- if(rlcd.isOK()) {
- if(!rlcd.isEmpty()) {
- return mapper.cert(rlcd.value, certs);
- }
- return Result.ok(certs);
- } else {
- return Result.err(rlcd);
- }
- } finally {
- tt.done();
- }
-
- }
-
- @ApiDoc(
- method = PUT,
- path = "/authn/cred",
- params = {},
- expectedCode = 200,
- errorCodes = {300,403,404,406},
- text = { "Reset a Credential Password. If multiple credentials exist for this",
- "ID, you will need to specify which entry you are resetting in the",
- "CredRequest object"
- }
- )
- @Override
- public Result<Void> changeUserCred(final AuthzTrans trans, REQUEST from) {
- final String cmdDescription = "Update User Credential";
- TimeTaken tt = trans.start(cmdDescription, Env.SUB);
- try {
- Result<CredDAO.Data> rcred = mapper.cred(trans, from, true);
- if(rcred.isOKhasData()) {
- rcred = ques.userCredSetup(trans, rcred.value);
-
- final ServiceValidator v = new ServiceValidator();
-
- if(v.cred(trans, trans.org(),rcred,false).err()) {// Note: Creates have stricter Validations
- return Result.err(Status.ERR_BadData,v.errs());
- }
- Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, rcred.value.id);
- if(rlcd.notOKorIsEmpty()) {
- return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
- }
-
- MayChange mc = new MayChangeCred(trans, rcred.value);
- Result<?> rmc = mc.mayChange();
- if (rmc.notOK()) {
- return Result.err(rmc);
- }
-
- Result<Integer> ri = selectEntryIfMultiple((CredRequest)from, rlcd.value);
- if(ri.notOK()) {
- return Result.err(ri);
- }
- int entry = ri.value;
-
-
- final CredDAO.Data cred = rcred.value;
-
- Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from, rcred.value,false,
- new Mapper.Memo() {
- @Override
- public String get() {
- return cmdDescription + " [" +
- cred.id + '|'
- + cred.type + '|'
- + cred.expires + ']';
- }
- },
- mc);
-
- Result<List<NsDAO.Data>> nsr = ques.nsDAO.read(trans, rcred.value.ns);
- if(nsr.notOKorIsEmpty()) {
- return Result.err(nsr);
- }
-
- switch(fd.status) {
- case OK:
- Result<String> rfc = func.createFuture(trans, fd.value,
- rcred.value.id + '|' + rcred.value.type.toString() + '|' + rcred.value.expires,
- trans.user(), nsr.value.get(0), FUTURE_OP.U);
- if(rfc.isOK()) {
- return Result.err(Status.ACC_Future, "Credential Request [%s|%s|%s]",
- rcred.value.id,
- Integer.toString(rcred.value.type),
- rcred.value.expires.toString());
- } else {
- return Result.err(rfc);
- }
- case Status.ACC_Now:
- Result<?>udr = null;
- // If we are Resetting Password on behalf of someone else (am not the Admin)
- // use TempPassword Expiration time.
- Expiration exp;
- if(ques.isAdmin(trans, trans.user(), nsr.value.get(0).name)) {
- exp = Expiration.Password;
- } else {
- exp = Expiration.TempPassword;
- }
-
- Organization org = trans.org();
- CredDAO.Data current = rlcd.value.get(entry);
- // If user resets password in same day, we will have a primary key conflict, so subtract 1 day
- if (current.expires.equals(rcred.value.expires)
- && rlcd.value.get(entry).type==rcred.value.type) {
- GregorianCalendar gc = org.expiration(null, exp,rcred.value.id);
- gc = Chrono.firstMomentOfDay(gc);
- gc.set(GregorianCalendar.HOUR_OF_DAY, org.startOfDay());
- rcred.value.expires = new Date(gc.getTimeInMillis() - DAY_IN_MILLIS);
- } else {
- rcred.value.expires = org.expiration(null,exp).getTime();
- }
- // Copy in other fields 10/21/2016
- rcred.value.notes=current.notes;
-
- udr = ques.credDAO.create(trans, rcred.value);
- if(udr.isOK()) {
- udr = ques.credDAO.delete(trans, rlcd.value.get(entry),false);
- }
- if (udr.isOK()) {
- return Result.ok();
- }
-
- return Result.err(udr);
- default:
- return Result.err(fd);
- }
- } else {
- return Result.err(rcred);
- }
- } finally {
- tt.done();
- }
- }
-
- /*
- * Codify the way to get Either Choice Needed or actual Integer from Credit Request
- */
- private Result<Integer> selectEntryIfMultiple(final CredRequest cr, List<CredDAO.Data> lcd) {
- int entry = 0;
- if (lcd.size() > 1) {
- String inputOption = cr.getEntry();
- if (inputOption == null) {
- String message = selectCredFromList(lcd, false);
- String[] variables = buildVariables(lcd);
- return Result.err(Status.ERR_ChoiceNeeded, message, variables);
- } else {
- entry = Integer.parseInt(inputOption) - 1;
- }
- if (entry < 0 || entry >= lcd.size()) {
- return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
- }
- }
- return Result.ok(entry);
- }
-
- @ApiDoc(
- method = PUT,
- path = "/authn/cred/:days",
- params = {"days|string|true"},
- expectedCode = 200,
- errorCodes = {300,403,404,406},
- text = { "Extend a Credential Expiration Date. The intention of this API is",
- "to avoid an outage in PROD due to a Credential expiring before it",
- "can be configured correctly. Measures are being put in place ",
- "so that this is not abused."
- }
- )
- @Override
- public Result<Void> extendUserCred(final AuthzTrans trans, REQUEST from, String days) {
- TimeTaken tt = trans.start("Extend User Credential", Env.SUB);
- try {
- Result<CredDAO.Data> cred = mapper.cred(trans, from, false);
- Organization org = trans.org();
- final ServiceValidator v = new ServiceValidator();
- if(v.notOK(cred).err() ||
- v.nullOrBlank(cred.value.id, "Invalid ID").err() ||
- v.user(org,cred.value.id).err()) {
- return Result.err(Status.ERR_BadData,v.errs());
- }
-
- try {
- String reason;
- if ((reason=org.validate(trans, Policy.MAY_EXTEND_CRED_EXPIRES, new CassExecutor(trans,func)))!=null) {
- return Result.err(Status.ERR_Policy,reason);
- }
- } catch (Exception e) {
- String msg;
- trans.error().log(e, msg="Could not contact Organization for User Validation");
- return Result.err(Status.ERR_Denied, msg);
- }
-
- // Get the list of Cred Entries
- Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, cred.value.id);
- if(rlcd.notOKorIsEmpty()) {
- return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
- }
-
- //Need to do the "Pick Entry" mechanism
- Result<Integer> ri = selectEntryIfMultiple((CredRequest)from, rlcd.value);
- if(ri.notOK()) {
- return Result.err(ri);
- }
-
- CredDAO.Data found = rlcd.value.get(ri.value);
- CredDAO.Data cd = cred.value;
- // Copy over the cred
- cd.id = found.id;
- cd.cred = found.cred;
- cd.other = found.other;
- cd.type = found.type;
- cd.notes = found.notes;
- cd.ns = found.ns;
- cd.expires = org.expiration(null, Expiration.ExtendPassword,days).getTime();
-
- cred = ques.credDAO.create(trans, cd);
- if(cred.isOK()) {
- return Result.ok();
- }
- return Result.err(cred);
- } finally {
- tt.done();
- }
- }
-
- private String[] buildVariables(List<CredDAO.Data> value) {
- // ensure credentials are sorted so we can fully automate Cred regression test
- Collections.sort(value, new Comparator<CredDAO.Data>() {
- @Override
- public int compare(CredDAO.Data cred1, CredDAO.Data cred2) {
- return cred1.expires.compareTo(cred2.expires);
- }
- });
- String [] vars = new String[value.size()+1];
- vars[0]="Choice";
- for (int i = 0; i < value.size(); i++) {
- vars[i+1] = value.get(i).id + " " + value.get(i).type
- + " |" + value.get(i).expires;
- }
- return vars;
- }
-
- private String selectCredFromList(List<CredDAO.Data> value, boolean isDelete) {
- StringBuilder errMessage = new StringBuilder();
- String userPrompt = isDelete?"Select which cred to delete (set force=true to delete all):":"Select which cred to update:";
- int numSpaces = value.get(0).id.length() - "Id".length();
-
- errMessage.append(userPrompt + '\n');
- errMessage.append(" Id");
- for (int i = 0; i < numSpaces; i++) {
- errMessage.append(' ');
- }
- errMessage.append(" Type Expires" + '\n');
- for(int i=0;i<value.size();++i) {
- errMessage.append(" %s\n");
- }
- errMessage.append("Run same command again with chosen entry as last parameter");
-
- return errMessage.toString();
-
- }
-
- @ApiDoc(
- method = DELETE,
- path = "/authn/cred",
- params = {},
- expectedCode = 200,
- errorCodes = {300,403,404,406},
- text = { "Delete a Credential. If multiple credentials exist for this",
- "ID, you will need to specify which entry you are deleting in the",
- "CredRequest object."
- }
- )
- @Override
- public Result<Void> deleteUserCred(AuthzTrans trans, REQUEST from) {
- final Result<CredDAO.Data> cred = mapper.cred(trans, from, false);
- final Validator v = new ServiceValidator();
- if(v.nullOrBlank("cred", cred.value.id).err()) {
- return Result.err(Status.ERR_BadData,v.errs());
- }
-
- Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, cred.value.id);
- if(rlcd.notOKorIsEmpty()) {
- // Empty Creds should have no user_roles.
- Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO.readByUser(trans, cred.value.id);
- if(rlurd.isOK()) {
- for(UserRoleDAO.Data data : rlurd.value) {
- ques.userRoleDAO.delete(trans, data, false);
- }
- }
- return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
- }
- boolean isLastCred = rlcd.value.size()==1;
-
- MayChange mc = new MayChangeCred(trans,cred.value);
- Result<?> rmc = mc.mayChange();
- if (rmc.notOK()) {
- return Result.err(rmc);
- }
-
- int entry = 0;
- if(!trans.requested(force)) {
- if (rlcd.value.size() > 1) {
- CredRequest cr = (CredRequest)from;
- String inputOption = cr.getEntry();
- if (inputOption == null) {
- String message = selectCredFromList(rlcd.value, true);
- String[] variables = buildVariables(rlcd.value);
- return Result.err(Status.ERR_ChoiceNeeded, message, variables);
- } else {
- try {
- if(inputOption.length()>5) { // should be a date
- Date d = Chrono.xmlDatatypeFactory.newXMLGregorianCalendar(inputOption).toGregorianCalendar().getTime();
- entry = 0;
- for(CredDAO.Data cd : rlcd.value) {
- if(cd.type.equals(cr.getType()) && cd.expires.equals(d)) {
- break;
- }
- ++entry;
- }
- } else {
- entry = Integer.parseInt(inputOption) - 1;
- }
- } catch(NullPointerException e) {
- return Result.err(Status.ERR_BadData, "Invalid Date Format for Entry");
- } catch(NumberFormatException e) {
- return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
- }
- }
- isLastCred = (entry==-1)?true:false;
- } else {
- isLastCred = true;
- }
- if (entry < -1 || entry >= rlcd.value.size()) {
- return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
- }
- }
-
- Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from,cred.value,false,
- new Mapper.Memo() {
- @Override
- public String get() {
- return "Delete Credential [" +
- cred.value.id +
- ']';
- }
- },
- mc);
-
- Result<List<NsDAO.Data>> nsr = ques.nsDAO.read(trans, cred.value.ns);
- if(nsr.notOKorIsEmpty()) {
- return Result.err(nsr);
- }
-
- switch(fd.status) {
- case OK:
- Result<String> rfc = func.createFuture(trans, fd.value, cred.value.id,
- trans.user(), nsr.value.get(0), FUTURE_OP.D);
-
- if(rfc.isOK()) {
- return Result.err(Status.ACC_Future, "Credential Delete [%s] is saved for future processing",cred.value.id);
- } else {
- return Result.err(rfc);
- }
- case Status.ACC_Now:
- Result<?>udr = null;
- if (!trans.requested(force)) {
- if(entry<0 || entry >= rlcd.value.size()) {
- return Result.err(Status.ERR_BadData,"Invalid Choice [" + entry + "] chosen for Delete [%s] is saved for future processing",cred.value.id);
- }
- udr = ques.credDAO.delete(trans, rlcd.value.get(entry),false);
- } else {
- for (CredDAO.Data curr : rlcd.value) {
- udr = ques.credDAO.delete(trans, curr, false);
- if (udr.notOK()) {
- return Result.err(udr);
- }
- }
- }
- if(isLastCred) {
- Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO.readByUser(trans, cred.value.id);
- if(rlurd.isOK()) {
- for(UserRoleDAO.Data data : rlurd.value) {
- ques.userRoleDAO.delete(trans, data, false);
- }
- }
- }
- if(udr==null) {
- return Result.err(Result.ERR_NotFound,"No User Data found");
- }
- if (udr.isOK()) {
- return Result.ok();
- }
- return Result.err(udr);
- default:
- return Result.err(fd);
- }
-
- }
-
-
- @Override
- public Result<Date> doesCredentialMatch(AuthzTrans trans, REQUEST credReq) {
- TimeTaken tt = trans.start("Does Credential Match", Env.SUB);
- try {
- // Note: Mapper assigns RAW type
- Result<CredDAO.Data> data = mapper.cred(trans, credReq,false);
- if(data.notOKorIsEmpty()) {
- return Result.err(data);
- }
- CredDAO.Data cred = data.value; // of the Mapped Cred
- if(cred.cred==null) {
- return Result.err(Result.ERR_BadData,"No Password");
- } else {
- return ques.doesUserCredMatch(trans, cred.id, cred.cred.array());
- }
-
- } catch (DAOException e) {
- trans.error().log(e,"Error looking up cred");
- return Result.err(Status.ERR_Denied,"Credential does not match");
- } finally {
- tt.done();
- }
- }
-
- @ApiDoc(
- method = GET,
- path = "/authn/basicAuth",
- params = {},
- expectedCode = 200,
- errorCodes = { 403 },
- text = { "!!!! DEPRECATED without X509 Authentication STOP USING THIS API BY DECEMBER 2017, or use Certificates !!!!\n"
- + "Use /authn/validate instead\n"
- + "Note: Validate a Password using BasicAuth Base64 encoded Header. This HTTP/S call is intended as a fast"
- + " User/Password lookup for Security Frameworks, and responds 200 if it passes BasicAuth "
- + "security, and 403 if it does not." }
- )
- private void basicAuth() {
- // This is a place holder for Documentation. The real BasicAuth API does not call Service.
- }
-
- @ApiDoc(
- method = POST,
- path = "/authn/validate",
- params = {},
- expectedCode = 200,
- errorCodes = { 403 },
- text = { "Validate a Credential given a Credential Structure. This is a more comprehensive validation, can "
- + "do more than BasicAuth as Credential types exp" }
- )
- @Override
- public Result<Date> validateBasicAuth(AuthzTrans trans, String basicAuth) {
- //TODO how to make sure people don't use this in browsers? Do we care?
- TimeTaken tt = trans.start("Validate Basic Auth", Env.SUB);
- try {
- BasicPrincipal bp = new BasicPrincipal(basicAuth,trans.org().getRealm());
- Result<Date> rq = ques.doesUserCredMatch(trans, bp.getName(), bp.getCred());
- // Note: Only want to log problem, don't want to send back to end user
- if(rq.isOK()) {
- return rq;
- } else {
- trans.audit().log(rq.errorString());
- }
- } catch (Exception e) {
- trans.warn().log(e);
- } finally {
- tt.done();
- }
- return Result.err(Status.ERR_Denied,"Bad Basic Auth");
- }
+ private class MayCreateCred implements MayChange {
+ private Result<NsDAO.Data> nsd;
+ private AuthzTrans trans;
+ private CredDAO.Data cred;
+ private Executor exec;
+
+ public MayCreateCred(AuthzTrans trans, CredDAO.Data cred, Executor exec) {
+ this.trans = trans;
+ this.cred = cred;
+ this.exec = exec;
+ }
+
+ @Override
+ public Result<?> mayChange() {
+ if(nsd==null) {
+ nsd = ques.validNSOfDomain(trans, cred.id);
+ }
+ // is Ns of CredID valid?
+ if(nsd.isOK()) {
+ try {
+ // Check Org Policy
+ if(trans.org().validate(trans,Policy.CREATE_MECHID, exec, cred.id)==null) {
+ return Result.ok();
+ } else {
+ Result<?> rmc = ques.mayUser(trans, trans.user(), nsd.value, Access.write);
+ if(rmc.isOKhasData()) {
+ return rmc;
+ }
+ }
+ } catch (Exception e) {
+ trans.warn().log(e);
+ }
+ } else {
+ trans.warn().log(nsd.errorString());
+ }
+ return Result.err(Status.ERR_Denied,"%s is not allowed to create %s in %s",trans.user(),cred.id,cred.ns);
+ }
+ }
+
+ private class MayChangeCred implements MayChange {
+
+ private Result<NsDAO.Data> nsd;
+ private AuthzTrans trans;
+ private CredDAO.Data cred;
+ public MayChangeCred(AuthzTrans trans, CredDAO.Data cred) {
+ this.trans = trans;
+ this.cred = cred;
+ }
+
+ @Override
+ public Result<?> mayChange() {
+ // User can change himself (but not create)
+ if(trans.user().equals(cred.id)) {
+ return Result.ok();
+ }
+ if(nsd==null) {
+ nsd = ques.validNSOfDomain(trans, cred.id);
+ }
+ // Get the Namespace
+ if(nsd.isOK()) {
+ if(ques.mayUser(trans, trans.user(), nsd.value,Access.write).isOK()) {
+ return Result.ok();
+ }
+ String user[] = Split.split('.',trans.user());
+ if(user.length>2) {
+ String company = user[user.length-1] + '.' + user[user.length-2];
+ if(ques.isGranted(trans, trans.user(), ROOT_NS,"password",company,"reset")) {
+ return Result.ok();
+ }
+ }
+ }
+ return Result.err(Status.ERR_Denied,"%s is not allowed to change %s in %s",trans.user(),cred.id,cred.ns);
+ }
+
+ }
+
+ private final long DAY_IN_MILLIS = 24*3600*1000L;
+
+ @ApiDoc(
+ method = POST,
+ path = "/authn/cred",
+ params = {},
+ expectedCode = 201,
+ errorCodes = {403,404,406,409},
+ text = { "A credential consists of:",
+ "<ul><li>id - the ID to create within AAF. The domain is in reverse",
+ "order of Namespace (i.e. Users of Namespace com.att.myapp would be",
+ "AB1234@myapp.att.com</li>",
+ "<li>password - Company Policy Compliant Password</li></ul>",
+ "Note: AAF does support multiple credentials with the same ID.",
+ "Check with your organization if you have this implemented."
+ }
+ )
+ @Override
+ public Result<Void> createUserCred(final AuthzTrans trans, REQUEST from) {
+ final String cmdDescription = ("Create User Credential");
+ TimeTaken tt = trans.start(cmdDescription, Env.SUB);
+
+ try {
+ Result<CredDAO.Data> rcred = mapper.cred(trans, from, true);
+ if(rcred.isOKhasData()) {
+ byte[] rawCred = rcred.value.cred.array();
+ rcred = ques.userCredSetup(trans, rcred.value);
+
+ final ServiceValidator v = new ServiceValidator();
+
+ if(v.cred(trans, trans.org(),rcred,true).err()) { // Note: Creates have stricter Validations
+ return Result.err(Status.ERR_BadData,v.errs());
+ }
+
+
+ // 2016-4 Jonathan, New Behavior - If MechID is not registered with Org, deny creation
+ Identity mechID = null;
+ Organization org = trans.org();
+ try {
+ mechID = org.getIdentity(trans, rcred.value.id);
+ } catch (Exception e1) {
+ trans.error().log(e1,rcred.value.id,"cannot be validated at this time");
+ }
+ if(mechID==null || !mechID.isFound()) {
+ return Result.err(Status.ERR_Policy,"MechIDs must be registered with %s before provisioning in AAF",org.getName());
+ }
+
+ Result<List<NsDAO.Data>> nsr = ques.nsDAO.read(trans, rcred.value.ns);
+ if(nsr.notOKorIsEmpty()) {
+ return Result.err(Status.ERR_NsNotFound,"Cannot provision %s on non-existent Namespace %s",mechID.id(),rcred.value.ns);
+ }
+
+
+ boolean firstID = false;
+ MayChange mc;
+
+ CassExecutor exec = new CassExecutor(trans, func);
+ Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, rcred.value.id);
+ if (rlcd.isOKhasData()) {
+ if (!org.canHaveMultipleCreds(rcred.value.id)) {
+ return Result.err(Status.ERR_ConflictAlreadyExists, "Credential exists");
+ }
+ Result<Boolean> rb;
+ for (CredDAO.Data curr : rlcd.value) {
+ // May not use the same password in the list
+ // Note: ASPR specifies character differences, but we don't actually store the
+ // password to validate char differences.
+
+ rb = ques.userCredCheck(trans, curr, rawCred);
+ if(rb.notOK()) {
+ return Result.err(rb);
+ } else if(rb.value){
+ return Result.err(Status.ERR_Policy, "Credential content cannot be reused.");
+ } else if (Chrono.dateOnlyStamp(curr.expires).equals(Chrono.dateOnlyStamp(rcred.value.expires)) && curr.type==rcred.value.type) {
+ return Result.err(Status.ERR_ConflictAlreadyExists, "Credential with same Expiration Date exists, use 'reset'");
+ }
+ }
+ } else {
+ try {
+ // 2016-04-12 Jonathan If Caller is the Sponsor and is also an Owner of NS, allow without special Perm
+ String theMechID = rcred.value.id;
+ Boolean otherMechIDs = false;
+ // find out if this is the only mechID. other MechIDs mean special handling (not automated)
+ for(CredDAO.Data cd : ques.credDAO.readNS(trans,nsr.value.get(0).name).value) {
+ if(!cd.id.equals(theMechID)) {
+ otherMechIDs = true;
+ break;
+ }
+ }
+ String reason;
+ // We can say "ID does not exist" here
+ if((reason=org.validate(trans, Policy.CREATE_MECHID, exec, theMechID,trans.user(),otherMechIDs.toString()))!=null) {
+ return Result.err(Status.ERR_Denied, reason);
+ }
+ firstID=true;
+ } catch (Exception e) {
+ return Result.err(e);
+ }
+ }
+
+ mc = new MayCreateCred(trans, rcred.value, exec);
+
+ final CredDAO.Data cdd = rcred.value;
+ Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from, rcred.value,false, // may want to enable in future.
+ new Mapper.Memo() {
+ @Override
+ public String get() {
+ return cmdDescription + " [" +
+ cdd.id + '|'
+ + cdd.type + '|'
+ + cdd.expires + ']';
+ }
+ },
+ mc);
+
+ switch(fd.status) {
+ case OK:
+ Result<String> rfc = func.createFuture(trans, fd.value,
+ rcred.value.id + '|' + rcred.value.type.toString() + '|' + rcred.value.expires,
+ trans.user(), nsr.value.get(0), FUTURE_OP.C);
+ if(rfc.isOK()) {
+ return Result.err(Status.ACC_Future, "Credential Request [%s|%s|%s] is saved for future processing",
+ rcred.value.id,
+ Integer.toString(rcred.value.type),
+ rcred.value.expires.toString());
+ } else {
+ return Result.err(rfc);
+ }
+ case Status.ACC_Now:
+ try {
+ if(firstID) {
+ // && !nsr.value.get(0).isAdmin(trans.getUserPrincipal().getName())) {
+ Result<List<String>> admins = func.getAdmins(trans, nsr.value.get(0).name, false);
+ // OK, it's a first ID, and not by NS Admin, so let's set TempPassword length
+ // Note, we only do this on First time, because of possibility of
+ // prematurely expiring a production id
+ if(admins.isOKhasData() && !admins.value.contains(trans.user())) {
+ rcred.value.expires = org.expiration(null, Expiration.TempPassword).getTime();
+ }
+ }
+ } catch (Exception e) {
+ trans.error().log(e, "While setting expiration to TempPassword");
+ }
+ Result<?>udr = ques.credDAO.create(trans, rcred.value);
+ if(udr.isOK()) {
+ return Result.ok();
+ }
+ return Result.err(udr);
+ default:
+ return Result.err(fd);
+ }
+
+ } else {
+ return Result.err(rcred);
+ }
+ } finally {
+ tt.done();
+ }
+ }
+
+ @ApiDoc(
+ method = GET,
+ path = "/authn/creds/ns/:ns",
+ params = {"ns|string|true"},
+ expectedCode = 200,
+ errorCodes = {403,404,406},
+ text = { "Return all IDs in Namespace :ns"
+ }
+ )
+ @Override
+ public Result<USERS> getCredsByNS(AuthzTrans trans, String ns) {
+ final Validator v = new ServiceValidator();
+ if(v.ns(ns).err()) {
+ return Result.err(Status.ERR_BadData,v.errs());
+ }
+
+ // check if user is allowed to view NS
+ Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
+ if(rnd.notOK()) {
+ return Result.err(rnd);
+ }
+ rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
+ if(rnd.notOK()) {
+ return Result.err(rnd);
+ }
+
+ TimeTaken tt = trans.start("MAP Creds by NS to Creds", Env.SUB);
+ try {
+ USERS users = mapper.newInstance(API.USERS);
+ Result<List<CredDAO.Data>> rlcd = ques.credDAO.readNS(trans, ns);
+
+ if(rlcd.isOK()) {
+ if(!rlcd.isEmpty()) {
+ return mapper.cred(rlcd.value, users);
+ }
+ return Result.ok(users);
+ } else {
+ return Result.err(rlcd);
+ }
+ } finally {
+ tt.done();
+ }
+
+ }
+
+ @ApiDoc(
+ method = GET,
+ path = "/authn/creds/id/:ns",
+ params = {"id|string|true"},
+ expectedCode = 200,
+ errorCodes = {403,404,406},
+ text = { "Return all IDs in for ID"
+ ,"(because IDs are multiple, due to multiple Expiration Dates)"
+ }
+ )
+ @Override
+ public Result<USERS> getCredsByID(AuthzTrans trans, String id) {
+ final Validator v = new ServiceValidator();
+ if(v.nullOrBlank("ID",id).err()) {
+ return Result.err(Status.ERR_BadData,v.errs());
+ }
+
+ String ns = Question.domain2ns(id);
+ // check if user is allowed to view NS
+ Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
+ if(rnd.notOK()) {
+ return Result.err(rnd);
+ }
+ rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
+ if(rnd.notOK()) {
+ return Result.err(rnd);
+ }
+
+ TimeTaken tt = trans.start("MAP Creds by ID to Creds", Env.SUB);
+ try {
+ USERS users = mapper.newInstance(API.USERS);
+ Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, id);
+
+ if(rlcd.isOK()) {
+ if(!rlcd.isEmpty()) {
+ return mapper.cred(rlcd.value, users);
+ }
+ return Result.ok(users);
+ } else {
+ return Result.err(rlcd);
+ }
+ } finally {
+ tt.done();
+ }
+
+ }
+
+ @ApiDoc(
+ method = GET,
+ path = "/authn/certs/id/:id",
+ params = {"id|string|true"},
+ expectedCode = 200,
+ errorCodes = {403,404,406},
+ text = { "Return Cert Info for ID"
+ }
+ )
+ @Override
+ public Result<CERTS> getCertInfoByID(AuthzTrans trans, HttpServletRequest req, String id) {
+ TimeTaken tt = trans.start("Get Cert Info by ID", Env.SUB);
+ try {
+ CERTS certs = mapper.newInstance(API.CERTS);
+ Result<List<CertDAO.Data>> rlcd = ques.certDAO.readID(trans, id);
+
+ if(rlcd.isOK()) {
+ if(!rlcd.isEmpty()) {
+ return mapper.cert(rlcd.value, certs);
+ }
+ return Result.ok(certs);
+ } else {
+ return Result.err(rlcd);
+ }
+ } finally {
+ tt.done();
+ }
+
+ }
+
+ @ApiDoc(
+ method = PUT,
+ path = "/authn/cred",
+ params = {},
+ expectedCode = 200,
+ errorCodes = {300,403,404,406},
+ text = { "Reset a Credential Password. If multiple credentials exist for this",
+ "ID, you will need to specify which entry you are resetting in the",
+ "CredRequest object"
+ }
+ )
+ @Override
+ public Result<Void> changeUserCred(final AuthzTrans trans, REQUEST from) {
+ final String cmdDescription = "Update User Credential";
+ TimeTaken tt = trans.start(cmdDescription, Env.SUB);
+ try {
+ Result<CredDAO.Data> rcred = mapper.cred(trans, from, true);
+ if(rcred.isOKhasData()) {
+ rcred = ques.userCredSetup(trans, rcred.value);
+
+ final ServiceValidator v = new ServiceValidator();
+
+ if(v.cred(trans, trans.org(),rcred,false).err()) {// Note: Creates have stricter Validations
+ return Result.err(Status.ERR_BadData,v.errs());
+ }
+ Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, rcred.value.id);
+ if(rlcd.notOKorIsEmpty()) {
+ return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
+ }
+
+ MayChange mc = new MayChangeCred(trans, rcred.value);
+ Result<?> rmc = mc.mayChange();
+ if (rmc.notOK()) {
+ return Result.err(rmc);
+ }
+
+ Result<Integer> ri = selectEntryIfMultiple((CredRequest)from, rlcd.value);
+ if(ri.notOK()) {
+ return Result.err(ri);
+ }
+ int entry = ri.value;
+
+
+ final CredDAO.Data cred = rcred.value;
+
+ Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from, rcred.value,false,
+ new Mapper.Memo() {
+ @Override
+ public String get() {
+ return cmdDescription + " [" +
+ cred.id + '|'
+ + cred.type + '|'
+ + cred.expires + ']';
+ }
+ },
+ mc);
+
+ Result<List<NsDAO.Data>> nsr = ques.nsDAO.read(trans, rcred.value.ns);
+ if(nsr.notOKorIsEmpty()) {
+ return Result.err(nsr);
+ }
+
+ switch(fd.status) {
+ case OK:
+ Result<String> rfc = func.createFuture(trans, fd.value,
+ rcred.value.id + '|' + rcred.value.type.toString() + '|' + rcred.value.expires,
+ trans.user(), nsr.value.get(0), FUTURE_OP.U);
+ if(rfc.isOK()) {
+ return Result.err(Status.ACC_Future, "Credential Request [%s|%s|%s]",
+ rcred.value.id,
+ Integer.toString(rcred.value.type),
+ rcred.value.expires.toString());
+ } else {
+ return Result.err(rfc);
+ }
+ case Status.ACC_Now:
+ Result<?>udr = null;
+ // If we are Resetting Password on behalf of someone else (am not the Admin)
+ // use TempPassword Expiration time.
+ Expiration exp;
+ if(ques.isAdmin(trans, trans.user(), nsr.value.get(0).name)) {
+ exp = Expiration.Password;
+ } else {
+ exp = Expiration.TempPassword;
+ }
+
+ Organization org = trans.org();
+ CredDAO.Data current = rlcd.value.get(entry);
+ // If user resets password in same day, we will have a primary key conflict, so subtract 1 day
+ if (current.expires.equals(rcred.value.expires)
+ && rlcd.value.get(entry).type==rcred.value.type) {
+ GregorianCalendar gc = org.expiration(null, exp,rcred.value.id);
+ gc = Chrono.firstMomentOfDay(gc);
+ gc.set(GregorianCalendar.HOUR_OF_DAY, org.startOfDay());
+ rcred.value.expires = new Date(gc.getTimeInMillis() - DAY_IN_MILLIS);
+ } else {
+ rcred.value.expires = org.expiration(null,exp).getTime();
+ }
+ // Copy in other fields 10/21/2016
+ rcred.value.notes=current.notes;
+
+ udr = ques.credDAO.create(trans, rcred.value);
+ if(udr.isOK()) {
+ udr = ques.credDAO.delete(trans, rlcd.value.get(entry),false);
+ }
+ if (udr.isOK()) {
+ return Result.ok();
+ }
+
+ return Result.err(udr);
+ default:
+ return Result.err(fd);
+ }
+ } else {
+ return Result.err(rcred);
+ }
+ } finally {
+ tt.done();
+ }
+ }
+
+ /*
+ * Codify the way to get Either Choice Needed or actual Integer from Credit Request
+ */
+ private Result<Integer> selectEntryIfMultiple(final CredRequest cr, List<CredDAO.Data> lcd) {
+ int entry = 0;
+ if (lcd.size() > 1) {
+ String inputOption = cr.getEntry();
+ if (inputOption == null) {
+ String message = selectCredFromList(lcd, false);
+ String[] variables = buildVariables(lcd);
+ return Result.err(Status.ERR_ChoiceNeeded, message, variables);
+ } else {
+ entry = Integer.parseInt(inputOption) - 1;
+ }
+ if (entry < 0 || entry >= lcd.size()) {
+ return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
+ }
+ }
+ return Result.ok(entry);
+ }
+
+ @ApiDoc(
+ method = PUT,
+ path = "/authn/cred/:days",
+ params = {"days|string|true"},
+ expectedCode = 200,
+ errorCodes = {300,403,404,406},
+ text = { "Extend a Credential Expiration Date. The intention of this API is",
+ "to avoid an outage in PROD due to a Credential expiring before it",
+ "can be configured correctly. Measures are being put in place ",
+ "so that this is not abused."
+ }
+ )
+ @Override
+ public Result<Void> extendUserCred(final AuthzTrans trans, REQUEST from, String days) {
+ TimeTaken tt = trans.start("Extend User Credential", Env.SUB);
+ try {
+ Result<CredDAO.Data> cred = mapper.cred(trans, from, false);
+ Organization org = trans.org();
+ final ServiceValidator v = new ServiceValidator();
+ if(v.notOK(cred).err() ||
+ v.nullOrBlank(cred.value.id, "Invalid ID").err() ||
+ v.user(org,cred.value.id).err()) {
+ return Result.err(Status.ERR_BadData,v.errs());
+ }
+
+ try {
+ String reason;
+ if ((reason=org.validate(trans, Policy.MAY_EXTEND_CRED_EXPIRES, new CassExecutor(trans,func)))!=null) {
+ return Result.err(Status.ERR_Policy,reason);
+ }
+ } catch (Exception e) {
+ String msg;
+ trans.error().log(e, msg="Could not contact Organization for User Validation");
+ return Result.err(Status.ERR_Denied, msg);
+ }
+
+ // Get the list of Cred Entries
+ Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, cred.value.id);
+ if(rlcd.notOKorIsEmpty()) {
+ return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
+ }
+
+ //Need to do the "Pick Entry" mechanism
+ Result<Integer> ri = selectEntryIfMultiple((CredRequest)from, rlcd.value);
+ if(ri.notOK()) {
+ return Result.err(ri);
+ }
+
+ CredDAO.Data found = rlcd.value.get(ri.value);
+ CredDAO.Data cd = cred.value;
+ // Copy over the cred
+ cd.id = found.id;
+ cd.cred = found.cred;
+ cd.other = found.other;
+ cd.type = found.type;
+ cd.notes = found.notes;
+ cd.ns = found.ns;
+ cd.expires = org.expiration(null, Expiration.ExtendPassword,days).getTime();
+
+ cred = ques.credDAO.create(trans, cd);
+ if(cred.isOK()) {
+ return Result.ok();
+ }
+ return Result.err(cred);
+ } finally {
+ tt.done();
+ }
+ }
+
+ private String[] buildVariables(List<CredDAO.Data> value) {
+ // ensure credentials are sorted so we can fully automate Cred regression test
+ Collections.sort(value, new Comparator<CredDAO.Data>() {
+ @Override
+ public int compare(CredDAO.Data cred1, CredDAO.Data cred2) {
+ return cred1.expires.compareTo(cred2.expires);
+ }
+ });
+ String [] vars = new String[value.size()+1];
+ vars[0]="Choice";
+ for (int i = 0; i < value.size(); i++) {
+ vars[i+1] = value.get(i).id + " " + value.get(i).type
+ + " |" + value.get(i).expires;
+ }
+ return vars;
+ }
+
+ private String selectCredFromList(List<CredDAO.Data> value, boolean isDelete) {
+ StringBuilder errMessage = new StringBuilder();
+ String userPrompt = isDelete?"Select which cred to delete (set force=true to delete all):":"Select which cred to update:";
+ int numSpaces = value.get(0).id.length() - "Id".length();
+
+ errMessage.append(userPrompt + '\n');
+ errMessage.append(" Id");
+ for (int i = 0; i < numSpaces; i++) {
+ errMessage.append(' ');
+ }
+ errMessage.append(" Type Expires" + '\n');
+ for(int i=0;i<value.size();++i) {
+ errMessage.append(" %s\n");
+ }
+ errMessage.append("Run same command again with chosen entry as last parameter");
+
+ return errMessage.toString();
+
+ }
+
+ @ApiDoc(
+ method = DELETE,
+ path = "/authn/cred",
+ params = {},
+ expectedCode = 200,
+ errorCodes = {300,403,404,406},
+ text = { "Delete a Credential. If multiple credentials exist for this",
+ "ID, you will need to specify which entry you are deleting in the",
+ "CredRequest object."
+ }
+ )
+ @Override
+ public Result<Void> deleteUserCred(AuthzTrans trans, REQUEST from) {
+ final Result<CredDAO.Data> cred = mapper.cred(trans, from, false);
+ final Validator v = new ServiceValidator();
+ if(v.nullOrBlank("cred", cred.value.id).err()) {
+ return Result.err(Status.ERR_BadData,v.errs());
+ }
+
+ Result<List<CredDAO.Data>> rlcd = ques.credDAO.readID(trans, cred.value.id);
+ if(rlcd.notOKorIsEmpty()) {
+ // Empty Creds should have no user_roles.
+ Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO.readByUser(trans, cred.value.id);
+ if(rlurd.isOK()) {
+ for(UserRoleDAO.Data data : rlurd.value) {
+ ques.userRoleDAO.delete(trans, data, false);
+ }
+ }
+ return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
+ }
+ boolean isLastCred = rlcd.value.size()==1;
+
+ MayChange mc = new MayChangeCred(trans,cred.value);
+ Result<?> rmc = mc.mayChange();
+ if (rmc.notOK()) {
+ return Result.err(rmc);
+ }
+
+ int entry = 0;
+ if(!trans.requested(force)) {
+ if (rlcd.value.size() > 1) {
+ CredRequest cr = (CredRequest)from;
+ String inputOption = cr.getEntry();
+ if (inputOption == null) {
+ String message = selectCredFromList(rlcd.value, true);
+ String[] variables = buildVariables(rlcd.value);
+ return Result.err(Status.ERR_ChoiceNeeded, message, variables);
+ } else {
+ try {
+ if(inputOption.length()>5) { // should be a date
+ Date d = Chrono.xmlDatatypeFactory.newXMLGregorianCalendar(inputOption).toGregorianCalendar().getTime();
+ entry = 0;
+ for(CredDAO.Data cd : rlcd.value) {
+ if(cd.type.equals(cr.getType()) && cd.expires.equals(d)) {
+ break;
+ }
+ ++entry;
+ }
+ } else {
+ entry = Integer.parseInt(inputOption) - 1;
+ }
+ } catch(NullPointerException e) {
+ return Result.err(Status.ERR_BadData, "Invalid Date Format for Entry");
+ } catch(NumberFormatException e) {
+ return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
+ }
+ }
+ isLastCred = (entry==-1)?true:false;
+ } else {
+ isLastCred = true;
+ }
+ if (entry < -1 || entry >= rlcd.value.size()) {
+ return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
+ }
+ }
+
+ Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from,cred.value,false,
+ new Mapper.Memo() {
+ @Override
+ public String get() {
+ return "Delete Credential [" +
+ cred.value.id +
+ ']';
+ }
+ },
+ mc);
+
+ Result<List<NsDAO.Data>> nsr = ques.nsDAO.read(trans, cred.value.ns);
+ if(nsr.notOKorIsEmpty()) {
+ return Result.err(nsr);
+ }
+
+ switch(fd.status) {
+ case OK:
+ Result<String> rfc = func.createFuture(trans, fd.value, cred.value.id,
+ trans.user(), nsr.value.get(0), FUTURE_OP.D);
+
+ if(rfc.isOK()) {
+ return Result.err(Status.ACC_Future, "Credential Delete [%s] is saved for future processing",cred.value.id);
+ } else {
+ return Result.err(rfc);
+ }
+ case Status.ACC_Now:
+ Result<?>udr = null;
+ if (!trans.requested(force)) {
+ if(entry<0 || entry >= rlcd.value.size()) {
+ return Result.err(Status.ERR_BadData,"Invalid Choice [" + entry + "] chosen for Delete [%s] is saved for future processing",cred.value.id);
+ }
+ udr = ques.credDAO.delete(trans, rlcd.value.get(entry),false);
+ } else {
+ for (CredDAO.Data curr : rlcd.value) {
+ udr = ques.credDAO.delete(trans, curr, false);
+ if (udr.notOK()) {
+ return Result.err(udr);
+ }
+ }
+ }
+ if(isLastCred) {
+ Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO.readByUser(trans, cred.value.id);
+ if(rlurd.isOK()) {
+ for(UserRoleDAO.Data data : rlurd.value) {
+ ques.userRoleDAO.delete(trans, data, false);
+ }
+ }
+ }
+ if(udr==null) {
+ return Result.err(Result.ERR_NotFound,"No User Data found");
+ }
+ if (udr.isOK()) {
+ return Result.ok();
+ }
+ return Result.err(udr);
+ default:
+ return Result.err(fd);
+ }
+
+ }
+
+
+ @Override
+ public Result<Date> doesCredentialMatch(AuthzTrans trans, REQUEST credReq) {
+ TimeTaken tt = trans.start("Does Credential Match", Env.SUB);
+ try {
+ // Note: Mapper assigns RAW type
+ Result<CredDAO.Data> data = mapper.cred(trans, credReq,false);
+ if(data.notOKorIsEmpty()) {
+ return Result.err(data);
+ }
+ CredDAO.Data cred = data.value; // of the Mapped Cred
+ if(cred.cred==null) {
+ return Result.err(Result.ERR_BadData,"No Password");
+ } else {
+ return ques.doesUserCredMatch(trans, cred.id, cred.cred.array());
+ }
+
+ } catch (DAOException e) {
+ trans.error().log(e,"Error looking up cred");
+ return Result.err(Status.ERR_Denied,"Credential does not match");
+ } finally {
+ tt.done();
+ }
+ }
+
+ @ApiDoc(
+ method = GET,
+ path = "/authn/basicAuth",
+ params = {},
+ expectedCode = 200,
+ errorCodes = { 403 },
+ text = { "!!!! DEPRECATED without X509 Authentication STOP USING THIS API BY DECEMBER 2017, or use Certificates !!!!\n"
+ + "Use /authn/validate instead\n"
+ + "Note: Validate a Password using BasicAuth Base64 encoded Header. This HTTP/S call is intended as a fast"
+ + " User/Password lookup for Security Frameworks, and responds 200 if it passes BasicAuth "
+ + "security, and 403 if it does not." }
+ )
+ private void basicAuth() {
+ // This is a place holder for Documentation. The real BasicAuth API does not call Service.
+ }
+
+ @ApiDoc(
+ method = POST,
+ path = "/authn/validate",
+ params = {},
+ expectedCode = 200,
+ errorCodes = { 403 },
+ text = { "Validate a Credential given a Credential Structure. This is a more comprehensive validation, can "
+ + "do more than BasicAuth as Credential types exp" }
+ )
+ @Override
+ public Result<Date> validateBasicAuth(AuthzTrans trans, String basicAuth) {
+ //TODO how to make sure people don't use this in browsers? Do we care?
+ TimeTaken tt = trans.start("Validate Basic Auth", Env.SUB);
+ try {
+ BasicPrincipal bp = new BasicPrincipal(basicAuth,trans.org().getRealm());
+ Result<Date> rq = ques.doesUserCredMatch(trans, bp.getName(), bp.getCred());
+ // Note: Only want to log problem, don't want to send back to end user
+ if(rq.isOK()) {
+ return rq;
+ } else {
+ trans.audit().log(rq.errorString());
+ }
+ } catch (Exception e) {
+ trans.warn().log(e);
+ } finally {
+ tt.done();
+ }
+ return Result.err(Status.ERR_Denied,"Bad Basic Auth");
+ }