40616abc874d8133396d7701f085b27d345ee3c5
[aaf/authz.git] / auth / auth-cmd / src / main / java / org / onap / aaf / auth / cmd / Cmd.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.auth.cmd;
23
24 import java.io.PrintWriter;
25 import java.io.StringReader;
26 import java.sql.Date;
27 import java.text.DateFormat;
28 import java.text.SimpleDateFormat;
29 import java.util.ArrayList;
30 import java.util.Comparator;
31 import java.util.GregorianCalendar;
32 import java.util.List;
33 import java.util.Stack;
34 import java.util.concurrent.ConcurrentHashMap;
35
36 import org.onap.aaf.auth.env.AuthzEnv;
37 import org.onap.aaf.auth.org.OrganizationException;
38 import org.onap.aaf.auth.rserv.HttpMethods;
39 import org.onap.aaf.cadi.Access;
40 import org.onap.aaf.cadi.CadiException;
41 import org.onap.aaf.cadi.LocatorException;
42 import org.onap.aaf.cadi.client.Future;
43 import org.onap.aaf.cadi.client.Rcli;
44 import org.onap.aaf.cadi.client.Retryable;
45 import org.onap.aaf.cadi.config.Config;
46 import org.onap.aaf.cadi.http.HMangr;
47 import org.onap.aaf.misc.env.APIException;
48 import org.onap.aaf.misc.env.Data.TYPE;
49 import org.onap.aaf.misc.env.util.Chrono;
50 import org.onap.aaf.misc.rosetta.env.RosettaDF;
51
52 import aaf.v2_0.Error;
53 import aaf.v2_0.History;
54 import aaf.v2_0.History.Item;
55 import aaf.v2_0.Request;
56
57 import org.owasp.encoder.Encode;
58
59 public abstract class Cmd {
60     // Sonar claims DateFormat is not thread safe.  Leave as Instance Variable.
61     private final DateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
62     protected static final String BLANK = "";
63     protected static final String COMMA = ","; // for use in splits
64
65     protected static final int lineLength = 80;
66
67     private static final String hformat = "%-23s %-5s %-20s %-35s\n";
68
69     public static final String STARTDATE = "startdate";
70     public static final String ENDDATE = "enddate";
71
72     private String name;
73     private final Param[] params;
74     private int required;
75     protected final Cmd parent;
76     protected final List<Cmd> children;
77     private static final ConcurrentHashMap<Class<?>,RosettaDF<?>> dfs = new ConcurrentHashMap<>();
78     public final AAFcli aafcli;
79     protected Access access;
80     private AuthzEnv env;
81     private final String defaultRealm;
82
83     public Cmd(AAFcli aafcli, String name, Param ... params) {
84         this(aafcli,null, name,params);
85     }
86
87     public Cmd(Cmd parent, String name, Param ... params) {
88         this(parent.aafcli,parent, name,params);
89     }
90
91     Cmd(AAFcli aafcli, Cmd parent, String name, Param ... params) {
92         this.parent = parent;
93         this.aafcli = aafcli;
94         this.env = aafcli.env;
95         this.access = aafcli.access;
96         if (parent!=null) {
97             parent.children.add(this);
98         }
99         children = new ArrayList<>();
100         this.params = params;
101         this.name = name;
102         required=0;
103         for (Param p : params) {
104             if (p.required) {
105                 ++required;
106             }
107         }
108
109         String temp = access.getProperty(Config.AAF_DEFAULT_REALM,null);
110         if (temp!=null && !temp.startsWith("@")) {
111             defaultRealm = '@' + temp;
112         } else {
113             defaultRealm="<Set Default Realm>";
114         }
115     }
116
117     public final int exec(int idx, String ... args) throws CadiException, APIException, LocatorException {
118         if (args.length-idx<required) {
119             throw new CadiException(build(new StringBuilder("Too few args: "),null).toString());
120         }
121         return _exec(idx,args);
122     }
123
124     protected abstract int _exec(int idx, final String ... args) throws CadiException, APIException, LocatorException;
125
126     public void detailedHelp(int indent,StringBuilder sb) {
127     }
128
129     protected void detailLine(StringBuilder sb, int length, String s) {
130         multiChar(sb,length,' ',0);
131         sb.append(s);
132     }
133
134     public void apis(int indent,StringBuilder sb) {
135     }
136
137     protected void api(StringBuilder sb, int indent, HttpMethods meth, String pathInfo, Class<?> cls,boolean head) {
138         final String smeth = meth.name();
139         if (head) {
140             sb.append('\n');
141             detailLine(sb,indent,"APIs:");
142         }
143         indent+=2;
144         multiChar(sb,indent,' ',0);
145         sb.append(smeth);
146         sb.append(' ');
147         sb.append(pathInfo);
148         String cliString = aafcli.typeString(cls,true);
149         if (indent+smeth.length()+pathInfo.length()+cliString.length()+2>80) {
150             sb.append(" ...");
151             multiChar(sb,indent+3+smeth.length(),' ',0);
152         } else { // same line
153             sb.append(' ');
154         }
155         sb.append(cliString);
156     }
157
158     protected void multiChar(StringBuilder sb, int length, char c, int indent) {
159         sb.append('\n');
160         for (int i=0;i<indent;++i) {
161             sb.append(' ');
162         }
163         for (int i=indent;i<length;++i) {
164             sb.append(c);
165         }
166     }
167
168     public StringBuilder build(StringBuilder sb, StringBuilder detail) {
169         if (name!=null) {
170             sb.append(name);
171             sb.append(' ');
172         }
173         int line = sb.lastIndexOf("\n")+1;
174         if (line<0) {
175             line=0;
176         }
177         int indent = sb.length()-line;
178         for (Param p : params) {
179             sb.append(p.required?'<':'[');
180             sb.append(p.tag);
181             sb.append(p.required?"> ": "] ");
182         }
183
184         boolean first = true;
185         for (Cmd child : children) {
186             if (!(child instanceof DeprecatedCMD)) {
187                 if (first) {
188                     first = false;
189                 } else if (detail==null) {
190                     multiChar(sb,indent,' ',0);
191                 } else {
192                     // Write parents for Detailed Report
193                     Stack<String> stack = new Stack<>();
194                     for (Cmd c = child.parent;c!=null;c=c.parent) {
195                         if (c.name!=null) {
196                             stack.push(c.name);
197                         }
198                     }
199                     if (!stack.isEmpty()) {
200                         sb.append("  ");
201                         while (!stack.isEmpty()) {
202                             sb.append(stack.pop());
203                             sb.append(' ');
204                         }
205                     }
206                 }
207                 child.build(sb,detail);
208                 if (detail!=null) {
209                     child.detailedHelp(4, detail);
210                     // If Child wrote something, then add, bracketing by lines
211                     if (detail.length()>0) {
212                         multiChar(sb,80,'-',2);
213                         sb.append(detail);
214                         sb.append('\n');
215                         multiChar(sb,80,'-',2);
216                         sb.append('\n');
217                         detail.setLength(0); // reuse
218                     } else {
219                         sb.append('\n');
220                     }
221                 }
222             }
223         }
224         return sb;
225     }
226
227     protected void error(Future<?> future) {
228         StringBuilder sb = new StringBuilder("Failed");
229         String desc = future.body();
230         int code = future.code();
231         if (desc==null || desc.length()==0) {
232             withCode(sb,code);
233         } else if (desc.startsWith("{")) {
234             StringReader sr = new StringReader(desc);
235             try {
236                 // Note: 11-18-2013, JonathanGathman.  This rather convoluted Message Structure required by TSS Restful Specs, reflecting "Northbound" practices.
237                 Error err = getDF(Error.class).newData().in(TYPE.JSON).load(sr).asObject();
238                 sb.append(" [");
239                 sb.append(err.getMessageId());
240                 sb.append("]: ");
241                 String messageBody = err.getText();
242                 List<String> vars = err.getVariables();
243                 int pipe;
244                 for (int varCounter=0;varCounter<vars.size();) {
245                     String var = vars.get(varCounter);
246                     ++varCounter;
247                     if (messageBody.indexOf("%" + varCounter) >= 0) {
248                         if ((pipe = var.indexOf('|'))>=0) {  // In AAF, we use a PIPE for Choice
249                             if (aafcli.isTest()) {
250                                 String expiresStr = var.substring(pipe);
251                                 var = var.replace(expiresStr, "[Placeholder]");
252                             } else {
253                                 StringBuilder varsb = new StringBuilder(var);
254                                 varsb.deleteCharAt(pipe);
255                                 var = varsb.toString();
256                             }
257                             messageBody = messageBody.replace("%" + varCounter, varCounter-1 + ") " + var);
258                         } else {
259                             messageBody = messageBody.replace("%" + varCounter, var);
260                         }
261                     }
262                 }
263                 sb.append(messageBody);
264             } catch (Exception e) {
265                 withCode(sb,code);
266                 sb.append(" (Note: Details cannot be obtained from Error Structure)");
267             }
268         } else if (desc.startsWith("<html>")){ // Core Jetty, etc sends HTML for Browsers
269             withCode(sb,code);
270         } else {
271             sb.append(" with code ");
272             sb.append(code);
273             sb.append(", ");
274             sb.append(desc);
275         }
276         pw().println(Encode.forJava(sb.toString()));
277     }
278
279
280     private void withCode(StringBuilder sb, Integer code) {
281         sb.append(" with code ");
282         sb.append(code);
283         switch(code) {
284             case 401:
285                 sb.append(" (HTTP Not Authenticated)");
286                 break;
287             case 403:
288                 sb.append(" (HTTP Forbidden)");
289                 break;
290             case 404:
291                 sb.append(" (HTTP Not Found)");
292                 break;
293             default:
294         }
295     }
296
297     /**
298      * Consistently set start and end dates from Requests (all derived from Request)
299      * @param req
300      */
301     protected void setStartEnd(Request req) {
302         // Set Start/End Dates, if exist
303         String str;
304         if ((str = access.getProperty(Cmd.STARTDATE,null))!=null) {
305             req.setStart(Chrono.timeStamp(Date.valueOf(str)));
306         }
307
308         if ((str = access.getProperty(Cmd.ENDDATE,null))!=null) {
309             req.setEnd(Chrono.timeStamp(Date.valueOf(str)));
310         }
311     }
312
313     /**
314      * For Derived classes, who have ENV in this parent
315      *
316      * @param cls
317      * @return
318      * @throws APIException
319      */
320     protected <T> RosettaDF<T> getDF(Class<T> cls) throws APIException {
321         return getDF(env,cls);
322     }
323
324     /**
325      * This works well, making available for GUI, etc.
326      * @param env
327      * @param cls
328      * @return
329      * @throws APIException
330      */
331     @SuppressWarnings("unchecked")
332     public static <T> RosettaDF<T> getDF(AuthzEnv env, Class<T> cls) throws APIException {
333         RosettaDF<T> rdf = (RosettaDF<T>)dfs.get(cls);
334         if (rdf == null) {
335             rdf = env.newDataFactory(cls);
336             dfs.put(cls, rdf);
337         }
338         return rdf;
339     }
340
341     public void activity(History history, String header) {
342         if (history.getItem().isEmpty()) {
343             int start = header.indexOf('[');
344             if (start >= 0) {
345                 pw().println("No Activity Found for " + header.substring(start));
346             }
347         } else {
348             pw().println(header);
349             for (int i=0;i<lineLength;++i) {
350                 pw().print('-');
351             }
352             pw().println();
353
354             pw().format(hformat,"Date","Table","User","Memo");
355             for (int i=0;i<lineLength;++i) {
356                 pw().print('-');
357             }
358             pw().println();
359
360             // Save Server time by Sorting locally
361             List<Item> items = history.getItem();
362             java.util.Collections.sort(items, (Comparator<Item>) (o1, o2) -> o2.getTimestamp().compare(o1.getTimestamp()));
363
364             for (History.Item item : items) {
365                 GregorianCalendar gc = item.getTimestamp().toGregorianCalendar();
366                 pw().format(hformat,
367                     dateFmt.format(gc.getTime()),
368                     item.getTarget(),
369                     item.getUser(),
370                     item.getMemo());
371             }
372         }
373     }
374
375     /**
376      * Turn String Array into a | delimited String
377      * @param options
378      * @return
379      */
380     public static String optionsToString(String[] options) {
381         StringBuilder sb = new StringBuilder();
382         boolean first = true;
383         for (String s : options) {
384             if (first) {
385                 first = false;
386             } else {
387                 sb.append('|');
388             }
389             sb.append(s);
390         }
391         return sb.toString();
392     }
393
394     /**
395      * return which index number the Option matches.
396      *
397      * throws an Exception if not part of this Option Set
398      *
399      * @param options
400      * @param test
401      * @return
402      * @throws Exception
403      */
404     public int whichOption(String[] options, String test) throws CadiException {
405         for (int i=0;i<options.length;++i) {
406             if (options[i].equals(test)) {
407                 return i;
408             }
409         }
410         pw().printf("%s is not a valid cmd\n",test);
411         throw new CadiException(build(new StringBuilder("Invalid Option: "),null).toString());
412     }
413
414     protected HMangr hman() {
415         return aafcli.hman;
416     }
417
418     public<RET> RET same(Retryable<RET> retryable) throws APIException, CadiException, LocatorException {
419         // We're storing in AAFCli, because we know it's always the same, and single threaded
420         if (aafcli.prevCall!=null) {
421             retryable.item(aafcli.prevCall.item());
422             retryable.lastClient=aafcli.prevCall.lastClient;
423         }
424
425         RET ret = aafcli.hman.same(aafcli.ss,retryable);
426
427         // Store last call in AAFcli, because Cmds are all different instances.
428         aafcli.prevCall = retryable;
429         return ret;
430     }
431
432     public<RET> RET all(Retryable<RET> retryable) throws APIException, CadiException, LocatorException {
433         this.setQueryParamsOn(retryable.lastClient);
434         return aafcli.hman.all(aafcli.ss,retryable);
435     }
436
437     public<RET> RET oneOf(Retryable<RET> retryable,String host) throws APIException, CadiException, LocatorException {
438         this.setQueryParamsOn(retryable.lastClient);
439         return aafcli.hman.oneOf(aafcli.ss,retryable,true,host);
440     }
441
442     protected PrintWriter pw() {
443         return AAFcli.pw;
444     }
445
446     public String getName() {
447         return name;
448     }
449
450     public void reportHead(String ... str) {
451         pw().println();
452         boolean first = true;
453         int i=0;
454         for (String s : str) {
455             if (first) {
456                 if (++i>1) {
457                     first = false;
458                     pw().print("[");
459                 }
460             } else {
461                 pw().print("] [");
462             }
463             pw().print(s);
464         }
465         if (!first) {
466             pw().print(']');
467         }
468         pw().println();
469         reportLine();
470     }
471
472     public String reportColHead(String format, String ...  args) {
473         pw().format(format,(Object[])args);
474         reportLine();
475         return format;
476     }
477
478     public void reportLine() {
479         for (int i=0;i<lineLength;++i) {
480             pw().print('-');
481         }
482         pw().println();
483     }
484
485     protected void setQueryParamsOn(Rcli<?> rcli) {
486         StringBuilder sb=null;
487         String force;
488         if ((force=aafcli.forceString())!=null) {
489             sb = new StringBuilder("force=");
490             sb.append(force);
491         }
492         if (aafcli.addRequest()) {
493             if (sb==null) {
494                 sb = new StringBuilder("future=true");
495             } else {
496                 sb.append("&future=true");
497             }
498         }
499         if (sb!=null && rcli!=null) {
500             rcli.setQueryParams(sb.toString());
501         }
502     }
503 //
504 //    /**
505 //     * If Force is set, will return True once only, then revert to "FALSE".
506 //     *
507 //     * @return
508 //     */
509 //    protected String checkForce() {
510 //        if (TRUE.equalsIgnoreCase(env.getProperty(FORCE, FALSE))) {
511 //            env.setProperty(FORCE, FALSE);
512 //            return "true";
513 //        }
514 //        return FALSE;
515 //    }
516
517     public String toString() {
518         StringBuilder sb = new StringBuilder();
519         if (parent==null) { // ultimate parent
520             build(sb,null);
521             return sb.toString();
522         } else {
523             return parent.toString();
524         }
525     }
526
527     /**
528      * Appends shortID with Realm, but only when allowed by Organization
529      * @throws OrganizationException
530      */
531     public String fullID(String id) {
532         if (id != null) {
533             if (id.indexOf('@') < 0) {
534                 id+=defaultRealm;
535             } else {
536                 return id; // is already a full ID
537             }
538         }
539         return id;
540     }
541 }