2 * ============LICENSE_START====================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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====================================================
22 package org.onap.aaf.auth.gui.pages;
24 import java.io.IOException;
25 import java.net.ConnectException;
26 import java.util.ArrayList;
27 import java.util.Collections;
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;
53 import aaf.v2_0.Api.Route;
55 public class ApiDocs extends Page {
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")
64 + "<a href=\"./example/"
65 + "YXBwbGljYXRpb24vRXJyb3IreG1s"
66 // + Symm.base64noSplit().encode("application/Error+xml")
70 public ApiDocs(final AAF_GUI gui, final Page ... breadcrumbs) throws APIException, IOException {
71 super(gui.env,NAME,HREF, fields,
72 new BreadCrumbs(breadcrumbs),
74 new Table<AAF_GUI,AuthzTrans>("AAF API Reference",gui.env.newTransNoAvg(),new Model(), "class=std")
78 private static class Preamble extends NamedCode {
80 private static final String I = "i";
81 private final String fsUrl;
83 public Preamble(AAF_GUI gui) {
84 super(false, "preamble");
85 fsUrl = gui.access.getProperty(Config.AAF_URL_FS, "/theme");
89 public void code(Cache<HTMLGen> cache, HTMLGen xgen) throws APIException, IOException {
90 xgen.leaf(HTMLGen.H1).text("AAF 2.0 RESTful interface").end()
92 xgen.leaf(HTMLGen.H2).text("Accessing RESTful").end();
94 .leaf(HTMLGen.LI).text("AAF RESTful service is secured by the following:").end()
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()
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()
103 .leaf(HTMLGen.LI).text("Responses").end()
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()
111 .leaf(HTMLGen.LI).leaf(HTMLGen.A,"href=" + fsUrl + "/aaf_2_0.xsd").text("API 2.0").end().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()
118 .leaf(HTMLGen.LI).text("Character Restrictions").end()
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()
135 The Content is defined in the AAF XSD - TODO Add aaf.xsd”;
136 Character Restrictions
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
147 * Implement the Table Content for Permissions by User
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("");
157 public String[] headers() {
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<>();
172 final TimeTaken tt = trans.start("AAF APIs",Env.REMOTE);
174 gui.clientAsUser(trans.getUserPrincipal(), new Retryable<Void>() {
175 @SuppressWarnings("unchecked")
177 public Void code(Rcli<?> client) throws CadiException, ConnectException, APIException {
178 Future<Api> fa = client.read("/api",gui.getDF(Api.class));
181 TimeTaken tt2 = trans.start("Load Data", Env.SUB);
183 if (fa.value!=null)for (Route r : fa.value.getRoute()) {
184 String path = r.getPath();
186 StringBuilder desc = new StringBuilder();
188 desc.append("<p class=double>");
189 desc.append(r.getDesc());
191 if (!r.getComments().isEmpty()) {
192 for (String ct : r.getComments()) {
193 desc.append("</p><p class=api_comment>");
198 if (!r.getParam().isEmpty()) {
199 desc.append("<hr><p class=api_label>Parameters</p>");
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]);
206 desc.append(param[1]);
207 if ("true".equalsIgnoreCase(param[2])) {
208 desc.append(" (Required)");
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());
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()) {
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");
238 for (String ct : r.getContentType()) {
239 if (ct.contains("version=2")) {
240 desc.append("</p><p class=api_contentType><a href=\"./example/");
242 desc.append(Symm.base64noSplit.encode(ct));
243 } catch (IOException e) {
244 throw new CadiException(e);
254 AbsCell[] sa = new AbsCell[] {
256 new TextCell(r.getMeth(),"class=right"),
257 new TextCell(r.getPath()),
258 new TextCell(desc.toString()),
261 if (path.startsWith("/authz/perm")) {
262 sa[0] = perms.isEmpty()?new TextCell("PERMISSION"):BLANK;
264 } else if (path.startsWith("/authz/role") || path.startsWith("/authz/userRole")) {
265 sa[0] = roles.isEmpty()?new TextCell("ROLE"):BLANK;
267 } else if (path.startsWith("/authz/ns")) {
268 sa[0] = ns.isEmpty()?new TextCell("NAMESPACE"):BLANK;
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;
276 sa[0] = aafOnly.isEmpty()?new TextCell("AAF ONLY"):BLANK;
280 //TODO if (trans.fish(p))
281 prepare(rv, perms,roles,ns,user);
286 gui.writeError(trans, fa, null, 0);
291 } catch (Exception e) {
292 trans.error().log(e.getMessage());
297 return new Cells(rv,null);
300 @SuppressWarnings("unchecked")
301 private void prepare(ArrayList<AbsCell[]> rv, ArrayList<AbsCell[]> ... all) {
304 for (ArrayList<AbsCell[]> al : all) {
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);
314 return (HttpMethods.valueOf(((TextCell)ca1[1]).name).compareTo(
315 HttpMethods.valueOf(((TextCell)ca2[1]).name)));