39e4d0b662a32a9c326a0eca2a53142c682ed20e
[aaf/authz.git] / auth / auth-gui / src / main / java / org / onap / aaf / auth / gui / pages / ApiDocs.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.gui.pages;
23
24 import java.io.IOException;
25 import java.net.ConnectException;
26 import java.util.ArrayList;
27 import java.util.Collections;
28
29 import org.onap.aaf.auth.env.AuthzTrans;
30 import org.onap.aaf.auth.gui.AAF_GUI;
31 import org.onap.aaf.auth.gui.BreadCrumbs;
32 import org.onap.aaf.auth.gui.NamedCode;
33 import org.onap.aaf.auth.gui.Page;
34 import org.onap.aaf.auth.gui.Table;
35 import org.onap.aaf.auth.gui.Table.Cells;
36 import org.onap.aaf.auth.gui.table.AbsCell;
37 import org.onap.aaf.auth.gui.table.TableData;
38 import org.onap.aaf.auth.gui.table.TextCell;
39 import org.onap.aaf.auth.rserv.HttpMethods;
40 import org.onap.aaf.cadi.CadiException;
41 import org.onap.aaf.cadi.Symm;
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.misc.env.APIException;
47 import org.onap.aaf.misc.env.Env;
48 import org.onap.aaf.misc.env.TimeTaken;
49 import org.onap.aaf.misc.xgen.Cache;
50 import org.onap.aaf.misc.xgen.html.HTMLGen;
51
52 import aaf.v2_0.Api;
53 import aaf.v2_0.Api.Route;
54
55 public class ApiDocs extends Page {
56     // Package on purpose
57     private static final String HREF = "/gui/api";
58     private static final String NAME = "AAF RESTful API";
59     private static final String fields[] = {};
60     private static final String ERROR_LINK = "<a href=\"./example/"
61             + "YXBwbGljYXRpb24vRXJyb3IranNvbg=="
62 //            + Symm.base64noSplit().encode("application/Error+json") 
63             + "\">JSON</a> "
64             + "<a href=\"./example/"
65             + "YXBwbGljYXRpb24vRXJyb3IreG1s"
66 //            + Symm.base64noSplit().encode("application/Error+xml") 
67             + "\">XML</a> ";
68
69
70     public ApiDocs(final AAF_GUI gui, final Page ... breadcrumbs) throws APIException, IOException {
71         super(gui.env,NAME,HREF, fields,
72             new BreadCrumbs(breadcrumbs),
73             new Preamble(gui),
74             new Table<AAF_GUI,AuthzTrans>("AAF API Reference",gui.env.newTransNoAvg(),new Model(), "class=std")
75             );
76     }
77
78     private static class Preamble extends NamedCode {
79
80         private static final String I = "i";
81         private final String fsUrl;
82
83         public Preamble(AAF_GUI gui) {
84             super(false, "preamble");
85             fsUrl = gui.access.getProperty(Config.AAF_URL_FS, "/theme");
86         }
87
88         @Override
89         public void code(Cache<HTMLGen> cache, HTMLGen xgen) throws APIException, IOException {
90             xgen.leaf(HTMLGen.H1).text("AAF 2.0 RESTful interface").end()
91                 .hr();
92             xgen.leaf(HTMLGen.H2).text("Accessing RESTful").end();
93             xgen.incr(HTMLGen.UL)
94                     .leaf(HTMLGen.LI).text("AAF RESTful service is secured by the following:").end()
95                     .incr(HTMLGen.UL)
96                         .leaf(HTMLGen.LI).text("The Client must utilize HTTP/S. Non Secure HTTP is not acceptable").end()
97                         .leaf(HTMLGen.LI).text("The Client MUST supply an Identity validated by one of the following mechanisms").end()
98                         .incr(HTMLGen.UL)
99                             .leaf(HTMLGen.LI).text("BASIC AUTH protocol using Organization Registered AppID, provisioned in AAF").end()
100                             .leaf(HTMLGen.LI).text("(Near Future) Application level Certificate").end()
101                         .end()
102                     .end()
103                     .leaf(HTMLGen.LI).text("Responses").end()
104                     .incr(HTMLGen.UL)
105                         .leaf(HTMLGen.LI).text("Each API Entity listed shows what structure will be accepted by service (ContentType) "
106                                 + "or responded with by service (Accept). Therefore, use these in making your call. Critical for PUT/POST.").end()
107                         .leaf(HTMLGen.LI).text("Each API call may respond with JSON or XML.  Choose the ContentType/Accept that has "
108                                 + "+json after the type for JSON or +xml after the Type for XML").end()
109                         .leaf(HTMLGen.LI).text("XSDs for Versions").end()
110                         .incr(HTMLGen.UL)
111                             .leaf(HTMLGen.LI).leaf(HTMLGen.A,"href=" + fsUrl + "/aaf_2_0.xsd").text("API 2.0").end().end()
112                         .end()
113                         .leaf(HTMLGen.LI).text("AAF can support multiple Versions of the API.  Choose the ContentType/Accept that has "
114                                 + "the appropriate version=?.?").end()
115                         .leaf(HTMLGen.LI).text("All Errors coming from AAF return AT&T Standard Error Message as a String: " + ERROR_LINK
116                                 + " (does not apply to errors from Container)").end()
117                     .end()
118                     .leaf(HTMLGen.LI).text("Character Restrictions").end()
119                     .incr(HTMLGen.UL)
120                         .leaf(HTMLGen.LI).text("Character Restrictions must depend on the Enforcement Point used").end()
121                         .leaf(HTMLGen.LI).text("Most AAF usage will be AAF Enforcement Point Characters for Instance and Action are:")
122                             .br().br().leaf(I).text("a-zA-Z0-9,.()_-=%").end()
123                             .br().br().text("For Instance, you may declare a multi-dimensional key with : (colon) separator, example:").end()
124                             .br().leaf(I).text(":myCluster:myKeyspace").end()
125                             .br().br().text("The * (asterix) may be used as a wild-card by itself or within the multi-dimensional key, example:")
126                             .br().leaf(I).text(":myCluster:*").end()
127                             .br().br().text("The % (percent) character can be used as an Escape Character. Applications can use % followed by 2 hexadecimal "
128                                     + "digits to cover odd keys.  It is their code, however, which must translate.")
129                             .br().br().text("The = (equals) is allowed so that Applications can pass Base64 encodations of binary keys").end()
130                         .leaf(HTMLGen.LI).text("Ask for a Consultation on how these are typically used, or, if your tool is the only Enforcement Point, if set may be expanded").end()
131                     .end()
132                 .end();
133             /*
134
135             The Content is defined in the AAF XSD - TODO Add aaf.xsd”;
136             Character Restrictions
137
138             URLs impose restrictions on characters which have specific meanings. This means you cannot have these characters in the Field Content you send
139             “#” is a “Fragment URL”, or anchor. Content after this Character is not sent. AAF cannot do anything about this… don’t use it.
140             “?=&”. These are used to delineate Parameters.
141             “/“ is used to separate fields
142             */
143         }
144
145     };
146     /**
147      * Implement the Table Content for Permissions by User
148      *
149      * @author Jonathan
150      *
151      */
152     private static class Model extends TableData<AAF_GUI,AuthzTrans> {
153         public static final String[] HEADERS = new String[] {"Entity","Method","Path Info","Description"};
154         private static final TextCell BLANK = new TextCell("");
155
156         @Override
157         public String[] headers() {
158             return HEADERS;
159         }
160
161
162         @Override
163         public Cells get(final AuthzTrans trans, final AAF_GUI gui) {
164             final ArrayList<AbsCell[]> ns = new ArrayList<>();
165             final ArrayList<AbsCell[]> perms = new ArrayList<>();
166             final ArrayList<AbsCell[]> roles = new ArrayList<>();
167             final ArrayList<AbsCell[]> user = new ArrayList<>();
168             final ArrayList<AbsCell[]> aafOnly = new ArrayList<>();
169             final ArrayList<AbsCell[]> rv = new ArrayList<>();
170
171
172             final TimeTaken tt = trans.start("AAF APIs",Env.REMOTE);
173             try {
174                 gui.clientAsUser(trans.getUserPrincipal(), new Retryable<Void>() {
175                     @SuppressWarnings("unchecked")
176                     @Override
177                     public Void code(Rcli<?> client) throws CadiException, ConnectException, APIException {
178                         Future<Api> fa = client.read("/api",gui.getDF(Api.class));
179                         if (fa.get(5000)) {
180                             tt.done();
181                             TimeTaken tt2 = trans.start("Load Data", Env.SUB);
182                             try {
183                                 if (fa.value!=null)for (Route r : fa.value.getRoute()) {
184                                     String path = r.getPath();
185                                     // Build info
186                                     StringBuilder desc = new StringBuilder();
187
188                                     desc.append("<p class=double>");
189                                     desc.append(r.getDesc());
190
191                                     if (!r.getComments().isEmpty()) {
192                                         for (String ct : r.getComments()) {
193                                             desc.append("</p><p class=api_comment>");
194                                             desc.append(ct);
195                                         }
196                                     }
197
198                                     if (!r.getParam().isEmpty()) {
199                                         desc.append("<hr><p class=api_label>Parameters</p>");
200
201                                         for (String params : r.getParam()) {
202                                             String param[] = params.split("\\s*\\|\\s*");
203                                             desc.append("</p><p class=api_contentType>");
204                                             desc.append(param[0]);
205                                             desc.append(" : ");
206                                             desc.append(param[1]);
207                                             if ("true".equalsIgnoreCase(param[2])) {
208                                                 desc.append(" (Required)");
209                                             }
210                                         }
211                                     }
212
213
214                                     if (r.getExpected()!=0) {
215                                         desc.append("</p><p class=api_label>Expected HTTP Code</p><p class=api_comment>");
216                                         desc.append(r.getExpected());
217                                     }
218
219                                     if (!r.getExplicitErr().isEmpty()) {
220                                         desc.append("</p><p class=api_label>Explicit HTTP Error Codes</p><p class=api_comment>");
221                                         boolean first = true;
222                                         for (int ee : r.getExplicitErr()) {
223                                             if (first) {
224                                                 first = false;
225                                             } else {
226                                                 desc.append(", ");
227                                             }
228                                             desc.append(ee);
229                                         }
230                                     }
231
232                                     desc.append("</p><p class=api_label>");
233                                     desc.append("GET".equals(r.getMeth())?"Accept:":"ContentType:");
234                                     Collections.sort(r.getContentType());
235                                     if (r.getPath().startsWith("/authn/basicAuth")) {
236                                         desc.append("</p><p class=api_contentType>text/plain");
237                                     }
238                                     for (String ct : r.getContentType()) {
239                                         if (ct.contains("version=2")) {
240                                             desc.append("</p><p class=api_contentType><a href=\"./example/");
241                                             try {
242                                                 desc.append(Symm.base64noSplit.encode(ct));
243                                             } catch (IOException e) {
244                                                 throw new CadiException(e);
245                                             }
246                                             desc.append("\"/>");
247                                             desc.append(ct);
248                                             desc.append("</a>");
249                                         }
250                                     }
251                                     desc.append("</p>");
252
253
254                                     AbsCell[] sa = new AbsCell[] {
255                                         null,
256                                         new TextCell(r.getMeth(),"class=right"),
257                                         new TextCell(r.getPath()),
258                                         new TextCell(desc.toString()),
259                                     };
260
261                                     if (path.startsWith("/authz/perm")) {
262                                         sa[0] = perms.isEmpty()?new TextCell("PERMISSION"):BLANK;
263                                         perms.add(sa);
264                                     } else if (path.startsWith("/authz/role") || path.startsWith("/authz/userRole")) {
265                                         sa[0] = roles.isEmpty()?new TextCell("ROLE"):BLANK;
266                                         roles.add(sa);
267                                     } else if (path.startsWith("/authz/ns")) {
268                                         sa[0] = ns.isEmpty()?new TextCell("NAMESPACE"):BLANK;
269                                         ns.add(sa);
270                                     } else if (path.startsWith("/authn/basicAuth")
271                                         || path.startsWith("/authn/validate")
272                                         || path.startsWith("/authz/user")) {
273                                         sa[0] = user.isEmpty()?new TextCell("USER"):BLANK;
274                                         user.add(sa);
275                                     } else {
276                                         sa[0] = aafOnly.isEmpty()?new TextCell("AAF ONLY"):BLANK;
277                                         aafOnly.add(sa);
278                                     }
279                                 }
280                                 //TODO if (trans.fish(p))
281                                 prepare(rv, perms,roles,ns,user);
282                             } finally {
283                                 tt2.done();
284                             }
285                         } else {
286                             gui.writeError(trans, fa, null, 0);
287                         }
288                         return null;
289                     }
290                 });
291             } catch (Exception e) {
292                 trans.error().log(e.getMessage());
293             } finally {
294                 tt.done();
295             }
296
297             return new Cells(rv,null);
298         }
299
300         @SuppressWarnings("unchecked")
301         private void prepare(ArrayList<AbsCell[]> rv, ArrayList<AbsCell[]> ... all) {
302             AbsCell lead;
303             AbsCell[] row;
304             for (ArrayList<AbsCell[]> al : all) {
305                 if (al.size()>1) {
306                     row = al.get(0);
307                     lead = row[0];
308                     row[0]=BLANK;
309                     al.get(0).clone()[0]=BLANK;
310                     Collections.sort(al, (ca1, ca2) -> {
311                         int meth = ((TextCell)ca1[2]).name.compareTo(
312                             ((TextCell)ca2[2]).name);
313                         if (meth == 0) {
314                             return (HttpMethods.valueOf(((TextCell)ca1[1]).name).compareTo(
315                                 HttpMethods.valueOf(((TextCell)ca2[1]).name)));
316                         } else {
317                             return meth;
318                         }
319                     });
320                     // set new first row
321                     al.get(0)[0]=lead;
322
323                     rv.addAll(al);
324                 }
325             }
326         }
327     }
328 }