From feab2592f964ff68e04812aeafc807b4ce71048c Mon Sep 17 00:00:00 2001 From: Instrumental Date: Tue, 2 Jul 2019 16:19:59 -0500 Subject: update Approvals from Testing Issue-ID: AAF-857 Change-Id: Ic7780678991f5d3f25f2634d1a8b15cc03f62592 Signed-off-by: Instrumental --- .../java/org/onap/aaf/auth/dao/cass/FutureDAO.java | 8 +- .../main/java/org/onap/aaf/auth/cmd/AAFcli.java | 6 +- .../main/java/org/onap/aaf/auth/cmd/user/Cred.java | 25 +- .../onap/aaf/auth/server/AbsServiceStarter.java | 1 + .../aaf/auth/service/AuthzCassServiceImpl.java | 503 ++++++++++++--------- .../org/onap/aaf/auth/service/AuthzService.java | 2 +- .../aaf/auth/service/facade/AuthzFacadeImpl.java | 20 +- .../onap/aaf/auth/service/mapper/Mapper_2_0.java | 2 +- 8 files changed, 327 insertions(+), 240 deletions(-) (limited to 'auth') diff --git a/auth/auth-cass/src/main/java/org/onap/aaf/auth/dao/cass/FutureDAO.java b/auth/auth-cass/src/main/java/org/onap/aaf/auth/dao/cass/FutureDAO.java index 7831815f..72c0e98b 100644 --- a/auth/auth-cass/src/main/java/org/onap/aaf/auth/dao/cass/FutureDAO.java +++ b/auth/auth-cass/src/main/java/org/onap/aaf/auth/dao/cass/FutureDAO.java @@ -90,9 +90,9 @@ public class FutureDAO extends CassDAOImpl { data.memo = row.getString(2); data.start = row.getTimestamp(3); data.expires = row.getTimestamp(4); - data.construct = row.getBytes(5); - data.target_key = row.getString(6); - data.target_date = row.getTimestamp(7); + data.target_key = row.getString(5); + data.target_date = row.getTimestamp(6); + data.construct = row.getBytes(7); return data; } @@ -109,9 +109,9 @@ public class FutureDAO extends CassDAOImpl { obj[++idx] = data.memo; obj[++idx] = data.start; obj[++idx] = data.expires; - obj[++idx] = data.construct; obj[++idx] = data.target_key; obj[++idx] = data.target_date; + obj[++idx] = data.construct; } } diff --git a/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/AAFcli.java b/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/AAFcli.java index edbe2068..8fcea294 100644 --- a/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/AAFcli.java +++ b/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/AAFcli.java @@ -94,6 +94,10 @@ public class AAFcli { this(access,new AuthzEnv(access.getProperties()),wtr,hman, si,ss); } + public AuthzEnv env() { + return env; + } + public AAFcli(Access access, AuthzEnv env, Writer wtr, HMangr hman, SecurityInfoC si, SecuritySetter ss) throws APIException { this.env = env; this.access = access; @@ -328,7 +332,7 @@ public class AAFcli { Thread.sleep((long)(delay+globalDelay)); } } catch (Exception e) { - if (expect.contains(-1)) { + if (expect.contains(-1)) { pw.println(e.getMessage()); ret = -1; } else { diff --git a/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/user/Cred.java b/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/user/Cred.java index d41f0cf3..a1cb3e7a 100644 --- a/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/user/Cred.java +++ b/auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/user/Cred.java @@ -21,30 +21,37 @@ package org.onap.aaf.auth.cmd.user; +import java.util.List; + import org.onap.aaf.auth.cmd.AAFcli; import org.onap.aaf.auth.cmd.Cmd; import org.onap.aaf.auth.cmd.Param; import org.onap.aaf.auth.rserv.HttpMethods; import org.onap.aaf.cadi.CadiException; import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.aaf.client.ErrMessage; import org.onap.aaf.cadi.client.Future; import org.onap.aaf.cadi.client.Rcli; import org.onap.aaf.cadi.client.Retryable; import org.onap.aaf.misc.env.APIException; import aaf.v2_0.CredRequest; +import aaf.v2_0.Error; public class Cred extends Cmd { public static final String ATTEMPT_FAILED_SPECIFICS_WITHELD = "Attempt Failed. Specifics witheld."; private static final String CRED_PATH = "/authn/cred"; private static final String[] options = {"add","del","reset","extend"/*,"clean"*/}; - public Cred(User parent) { + private ErrMessage em; +// private RosettaDF errDF; + public Cred(User parent) throws APIException { super(parent,"cred", new Param(optionsToString(options),true), new Param("id",true), new Param("password (! D|E)",false), new Param("entry# (if multi)",false) ); + em = new ErrMessage(aafcli.env()); } @Override @@ -59,8 +66,9 @@ public class Cred extends Cmd { if (idx>=args.length) throw new CadiException("Password Required"); cr.setPassword(args[idx++]); } - if (args.length>idx) + if (args.length>idx) { cr.setEntry(args[idx]); + } // Set Start/End commands setStartEnd(cr); @@ -114,6 +122,19 @@ public class Cred extends Cmd { pw().println(']'); } else if (fp.code()==202) { pw().println("Credential Action Accepted, but requires Approvals before actualizing"); + } else if (fp.code()==300) { + Error err = em.getError(fp); + String text = err.getText(); + List vars = err.getVariables(); + + // IMPORTANT! We do this backward, because it is looking for string + // %1 or %13. If we replace %1 first, that messes up %13 + for(int i=vars.size()-1;i>0;--i) { + text = text.replace("%"+(i+1), (i<10?" ":"") + i+") " + vars.get(i)); + } + + text = text.replace("%1",vars.get(0)); + pw().println(text); } else if (fp.code()==406 && option==1) { pw().println("You cannot delete this Credential"); } else { diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsServiceStarter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsServiceStarter.java index f5831139..11ba6562 100644 --- a/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsServiceStarter.java +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsServiceStarter.java @@ -128,6 +128,7 @@ public abstract class AbsServiceStarter implements AuthzService { - private Mapper mapper; + private static final String TWO_SPACE = " "; + private Mapper mapper; @Override public Mapper mapper() {return mapper;} @@ -1988,6 +1991,9 @@ public class AuthzCassServiceImpl mayChange() { if (nsd==null) { nsd = ques.mayUser(trans, trans.user(), rpd.value, Access.write); + if(nsd.notOK()) { + trans.requested(REQD_TYPE.future,true); + } } return nsd; } @@ -1997,32 +2003,32 @@ public class AuthzCassServiceImpl rfc = func.createFuture(trans,fd.value, - rpd.value.fullPerm(), - trans.user(), - nsr.value.get(0), - FUTURE_OP.G); - if (rfc.isOK()) { - return Result.err(Status.ACC_Future, "Perm [%s.%s|%s|%s] is saved for future processing", - rpd.value.ns, - rpd.value.type, - rpd.value.instance, - rpd.value.action); - } else { - return Result.err(rfc); - } - case Status.ACC_Now: - Result rv = null; - if (createPerm!=null) {// has been validated for creating - rv = func.createPerm(trans, createPerm, false); - } - if (rv==null || rv.isOK()) { - rv = func.addPermToRole(trans, rrd.value, rpd.value, false); - } - return rv; - default: - return Result.err(fd); + case OK: + Result rfc = func.createFuture(trans,fd.value, + rpd.value.fullPerm(), + trans.user(), + nsr.value.get(0), + FUTURE_OP.G); + if (rfc.isOK()) { + return Result.err(Status.ACC_Future, "Perm [%s.%s|%s|%s] is saved for future processing", + rpd.value.ns, + rpd.value.type, + rpd.value.instance, + rpd.value.action); + } else { + return Result.err(rfc); + } + case Status.ACC_Now: + Result rv = null; + if (createPerm!=null) {// has been validated for creating + rv = func.createPerm(trans, createPerm, false); + } + if (rv==null || rv.isOK()) { + rv = func.addPermToRole(trans, rrd.value, rpd.value, false); + } + return rv; + default: + return Result.err(fd); } } @@ -2300,13 +2306,17 @@ public class AuthzCassServiceImpl nsd; + private static final String EXTEND = "extend"; + private static final String RESET = "reset"; + private static final String DELETE = "delete"; + private Result nsd; private AuthzTrans trans; private CredDAO.Data cred; - public MayChangeCred(AuthzTrans trans, CredDAO.Data cred) { + private String action; + public MayChangeCred(AuthzTrans trans, CredDAO.Data cred, String action) { this.trans = trans; this.cred = cred; + this.action = action; } @Override @@ -2317,17 +2327,38 @@ public class AuthzCassServiceImpl 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(); - } - } + String ns = nsd.value.name; + String user = trans.user(); + String company; + String temp[] = Split.split('.',ns); + switch(temp.length) { + case 0: + company = Defaults.AAF_NS; + break; + case 1: + company = temp[0]; + break; + default: + company = temp[0] + '.' + temp[1]; + } + switch(action) { + case DELETE: + if(ques.isOwner(trans, user,ns) || + ques.isAdmin(trans, user,ns) || + ques.isGranted(trans, user, ROOT_NS,"password",company,DELETE)) { + return Result.ok(); + } + break; + case RESET: + case EXTEND: + if (ques.isGranted(trans, trans.user(), ROOT_NS,"password",company,action)) { + return Result.ok(); + } + break; + } } - return Result.err(Status.ERR_Denied,"%s is not allowed to change %s in %s",trans.user(),cred.id,cred.ns); + return Result.err(Status.ERR_Denied,"%s is not allowed to %s %s in %s",trans.user(),action,cred.id,cred.ns); } - } private final long DAY_IN_MILLIS = 24*3600*1000L; @@ -2626,7 +2657,7 @@ public class AuthzCassServiceImpl changeUserCred(final AuthzTrans trans, REQUEST from) { + public Result resetUserCred(final AuthzTrans trans, REQUEST from) { final String cmdDescription = "Update User Credential"; TimeTaken tt = trans.start(cmdDescription, Env.SUB); try { @@ -2644,13 +2675,15 @@ public class AuthzCassServiceImpl rmc = mc.mayChange(); if (rmc.notOK()) { return Result.err(rmc); } - Result ri = selectEntryIfMultiple((CredRequest)from, rlcd.value); + List lcdd = filterList(rlcd.value,CredDAO.BASIC_AUTH, CredDAO.BASIC_AUTH_SHA256); + + Result ri = selectEntryIfMultiple((CredRequest)from, lcdd, MayChangeCred.RESET); if (ri.notOK()) { return Result.err(ri); } @@ -2733,27 +2766,6 @@ public class AuthzCassServiceImpl selectEntryIfMultiple(final CredRequest cr, List lcd) { - int entry = 0; - if (lcd.size() > 1) { - String inputOption = cr.getEntry(); - if (inputOption == null) { - String message = selectCredFromList(lcd, false); - Object[] 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", @@ -2795,14 +2807,18 @@ public class AuthzCassServiceImpl lcdd = filterList(rlcd.value,CredDAO.BASIC_AUTH, CredDAO.BASIC_AUTH_SHA256); //Need to do the "Pick Entry" mechanism - Result ri = selectEntryIfMultiple((CredRequest)from, rlcd.value); + // Note, this sorts + Result ri = selectEntryIfMultiple((CredRequest)from, lcdd, "extend"); if (ri.notOK()) { return Result.err(ri); } - CredDAO.Data found = rlcd.value.get(ri.value); + CredDAO.Data found = lcdd.get(ri.value); CredDAO.Data cd = cred.value; // Copy over the cred cd.id = found.id; @@ -2824,29 +2840,204 @@ public class AuthzCassServiceImpl value) { + @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 deleteUserCred(AuthzTrans trans, REQUEST from) { + final Result 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()); + } + + MayChange mc = new MayChangeCred(trans,cred.value,MayChangeCred.DELETE); + Result rmc = mc.mayChange(); + if (rmc.notOK()) { + return Result.err(rmc); + } + + Result> rlcd = ques.credDAO().readID(trans, cred.value.id); + if (rlcd.notOKorIsEmpty()) { + // Empty Creds should have no user_roles. + Result> 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; + + + int entry = 0; + if (!trans.requested(force)) { + if (rlcd.value.size() > 1) { + CredRequest cr = (CredRequest)from; + String inputOption = cr.getEntry(); + if (inputOption == null) { + List list = filterList(rlcd.value,CredDAO.BASIC_AUTH,CredDAO.BASIC_AUTH_SHA256,CredDAO.CERT_SHA256_RSA); + String message = selectCredFromList(list, MayChangeCred.DELETE); + Object[] variables = buildVariables(list); + 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 fd = mapper.future(trans,CredDAO.TABLE,from,cred.value,false, + () -> "Delete Credential [" + + cred.value.id + + ']', + mc); + + Result> nsr = ques.nsDAO().read(trans, cred.value.ns); + if (nsr.notOKorIsEmpty()) { + return Result.err(nsr); + } + + switch(fd.status) { + case OK: + Result 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: + Resultudr = 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> 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); + } + + } + + /* + * Codify the way to get Either Choice Needed or actual Integer from Credit Request + */ + private Result selectEntryIfMultiple(final CredRequest cr, List lcd, String action) { + int entry = 0; + if (lcd.size() > 1) { + String inputOption = cr.getEntry(); + if (inputOption == null) { + String message = selectCredFromList(lcd, action); + Object[] 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); + } + + private List filterList(List orig, Integer ... types) { + List rv = new ArrayList<>(); + for(CredDAO.Data cdd : orig) { + if(cdd!=null) { + for(int t : types) { + if(t==cdd.type) { + rv.add(cdd); + } + } + } + } + return rv; + } + + private String[] buildVariables(List value) { // ensure credentials are sorted so we can fully automate Cred regression test - Collections.sort(value, (cred1, cred2) -> cred1.expires.compareTo(cred2.expires)); + Collections.sort(value, (cred1, cred2) -> + cred1.type==cred2.type?cred2.expires.compareTo(cred1.expires): + cred1.type value, boolean isDelete) { + private String selectCredFromList(List value, String action) { StringBuilder errMessage = new StringBuilder(); - String userPrompt = isDelete?"Select which cred to delete (set force=true to delete all):":"Select which cred to update:"; + String userPrompt = MayChangeCred.DELETE.equals(action)? + "Select which cred to delete (set force=true to delete all):": + "Select which cred to " + action + ':'; int numSpaces = value.get(0).id.length() - "Id".length(); errMessage.append(userPrompt + '\n'); - errMessage.append(" Id"); + errMessage.append(" ID"); for (int i = 0; i < numSpaces; i++) { errMessage.append(' '); } - errMessage.append(" Type Expires" + '\n'); + errMessage.append(" Type Expires Tag " + '\n'); for (int i=0;i deleteUserCred(AuthzTrans trans, REQUEST from) { - final Result 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> rlcd = ques.credDAO().readID(trans, cred.value.id); - if (rlcd.notOKorIsEmpty()) { - // Empty Creds should have no user_roles. - Result> 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); - Object[] 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 fd = mapper.future(trans,CredDAO.TABLE,from,cred.value,false, - () -> "Delete Credential [" + - cred.value.id + - ']', - mc); - - Result> nsr = ques.nsDAO().read(trans, cred.value.ns); - if (nsr.notOKorIsEmpty()) { - return Result.err(nsr); - } - - switch(fd.status) { - case OK: - Result 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: - Resultudr = 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> 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 doesCredentialMatch(AuthzTrans trans, REQUEST credReq) { TimeTaken tt = trans.start("Does Credential Match", Env.SUB); @@ -3014,22 +3071,6 @@ public class AuthzCassServiceImpl changeUserCred(AuthzTrans trans, REQUEST from); + Result resetUserCred(AuthzTrans trans, REQUEST from); /** * diff --git a/auth/auth-service/src/main/java/org/onap/aaf/auth/service/facade/AuthzFacadeImpl.java b/auth/auth-service/src/main/java/org/onap/aaf/auth/service/facade/AuthzFacadeImpl.java index 253f91da..e85e52ec 100644 --- a/auth/auth-service/src/main/java/org/onap/aaf/auth/service/facade/AuthzFacadeImpl.java +++ b/auth/auth-service/src/main/java/org/onap/aaf/auth/service/facade/AuthzFacadeImpl.java @@ -44,7 +44,9 @@ import static org.onap.aaf.auth.layer.Result.OK; import java.io.IOException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -172,9 +174,15 @@ public abstract class AuthzFacadeImpl dlist = new ArrayList(); + String os; + for(Object s : result.variables) { + if(s!=null && (os=s.toString()).length()>0) { + dlist.add(os); + } + } + detail = new String[dlist.size()]; + dlist.toArray(detail); } //int httpstatus; @@ -280,10 +288,6 @@ public abstract class AuthzFacadeImpl