From 799f1236dceea6234561f39f73a317f8d93d71e6 Mon Sep 17 00:00:00 2001 From: IanHowell Date: Wed, 16 May 2018 09:14:10 -0500 Subject: [PATCH] Improve coverage of cadi-core * Reduced complexity of HttpEpiTaf and LoginPageTafResp * Applied ONAP's code style to HttpEpiTaf and LoginPageTafResp * wrote JUnits for HttpEpiTaf and LoginPageTafResp Issue-ID: AAF-225 Change-Id: I28059237e838d0276593d556d2e04fe3a7b852c6 Signed-off-by: IanHowell --- .../java/org/onap/aaf/cadi/taf/HttpEpiTaf.java | 117 +++++++++-------- .../org/onap/aaf/cadi/taf/LoginPageTafResp.java | 58 +++++---- .../org/onap/aaf/cadi/taf/test/JU_HttpEpiTaf.java | 145 +++++++++++++++++++++ .../aaf/cadi/taf/test/JU_LoginPageTafResp.java | 101 ++++++++++++++ 4 files changed, 345 insertions(+), 76 deletions(-) create mode 100644 cadi/core/src/test/java/org/onap/aaf/cadi/taf/test/JU_HttpEpiTaf.java create mode 100644 cadi/core/src/test/java/org/onap/aaf/cadi/taf/test/JU_LoginPageTafResp.java diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpEpiTaf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpEpiTaf.java index cc283973..5cd6323d 100644 --- a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpEpiTaf.java +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpEpiTaf.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -40,9 +40,9 @@ import org.onap.aaf.cadi.TrustChecker; /** * HttpEpiTaf - * + * * An extension of the basic "EpiTAF" concept, check known HTTP Related TAFs for valid credentials - * + * * @author Jonathan * */ @@ -51,10 +51,10 @@ public class HttpEpiTaf implements HttpTaf { private Access access; private Locator locator; private TrustChecker trustChecker; - + /** * HttpEpiTaf constructor - * + * * Construct the HttpEpiTaf from variable Http specific TAF parameters * @param tafs @@ -65,52 +65,52 @@ public class HttpEpiTaf implements HttpTaf { this.access = access; this.locator = locator; this.trustChecker = tc; - // Establish what Header Property to look for UserChain/Trust Props -// trustChainProp = access.getProperty(Config.CADI_TRUST_PROP, Config.CADI_TRUST_PROP_DEFAULT); + // Establish what Header Property to look for UserChain/Trust Props - if(tafs.length==0) throw new CadiException("Need at least one HttpTaf implementation in constructor"); + if (tafs.length == 0) { + throw new CadiException("Need at least one HttpTaf implementation in constructor"); + } } /** * validate - * - * Respond with the first Http specific TAF to authenticate user based on variable info + * + * Respond with the first Http specific TAF to authenticate user based on variable info * and "LifeForm" (is it a human behind a browser, or a server utilizing HTTP Protocol). - * + * * If there is no HttpTAF that can authenticate, respond with the first TAF that suggests it can * establish an Authentication conversation (TRY_AUTHENTICATING) (Examples include a redirect to CSP - * Servers for CSP Cookie, or BasicAuth 401 response, suggesting User/Password for given Realm + * Servers for CSP Cookie, or BasicAuth 401 response, suggesting User/Password for given Realm * submission - * + * * If no TAF declares either, respond with NullTafResp (which denies all questions) */ public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp) { // Given a LifeForm Neutral, for HTTP, we need to discover true Life-Form Readings - if(reading==LifeForm.LFN) { + if (reading == LifeForm.LFN) { reading = tricorderScan(req); } - TafResp tresp=null, firstTry = null; + TafResp tresp = null; + TafResp firstTry = null; List redirectables = null; - List trlog = access.willLog(Level.DEBUG)?new ArrayList():null; + List log = (access.willLog(Level.DEBUG)) ? new ArrayList() : null; try { - for(HttpTaf taf : tafs) { + for (HttpTaf taf : tafs) { tresp = taf.validate(reading, req, resp); - if(trlog!=null) { - trlog.add(tresp); - } + addToLog(log, tresp); switch(tresp.isAuthenticated()) { case TRY_ANOTHER_TAF: break; // and loop case TRY_AUTHENTICATING: - if(tresp instanceof Redirectable) { - if(redirectables==null) { - redirectables = new ArrayList(); + if (tresp instanceof Redirectable) { + if (redirectables == null) { + redirectables = new ArrayList<>(); } redirectables.add((Redirectable)tresp); - } else if(firstTry==null) { + } else if (firstTry == null) { firstTry = tresp; } - break; + break; case IS_AUTHENTICATED: tresp = trustChecker.mayTrust(tresp, req); return tresp; @@ -118,46 +118,42 @@ public class HttpEpiTaf implements HttpTaf { return tresp; } } - } finally { - if(trlog!=null) { - for( TafResp tr : trlog) { - access.log(Level.DEBUG, tr.desc()); - } - } + } finally { + printLog(log); } - - // If No TAFs configured, at this point. It is safer at this point to be "not validated", + + // If No TAFs configured, at this point. It is safer at this point to be "not validated", // rather than "let it go" // Note: if exists, there will always be more than 0 entries, according to above code - if(redirectables==null) { - return firstTry!=null?firstTry:NullTafResp.singleton(); + if (redirectables == null) { + return (firstTry != null) ? firstTry : NullTafResp.singleton(); } - + // If there is one Tryable entry then return it - if(redirectables.size()>1) { - return LoginPageTafResp.create(access,locator,resp,redirectables); + if (redirectables.size() > 1) { + return LoginPageTafResp.create(access, locator, resp, redirectables); } else { return redirectables.get(0); } } - + public boolean revalidate(Principal prin) throws Exception { return false; } /* * Since this is internal, we use a little Star Trek humor to indicate looking in the HTTP Request to see if we can determine what kind - * of "LifeForm" reading we can determine, i.e. is there a Human (CarbonBasedLifeForm) behind a browser, or is it mechanical + * of "LifeForm" reading we can determine, i.e. is there a Human (CarbonBasedLifeForm) behind a browser, or is it mechanical * id (SiliconBasedLifeForm)? This makes a difference in some Authentication, i.e CSP, which doesn't work well for SBLFs */ private LifeForm tricorderScan(HttpServletRequest req) { // For simplicity's sake, we'll say Humans use FQDNs, not IPs. - + // Current guess that only Browsers bother to set "Agent" codes that identify the kind of browser they are. // If mechanical frameworks are found that populate this, then more advanced analysis may be required // Jonathan 1/22/2013 String agent = req.getHeader("User-Agent"); - if(agent!=null && agent.startsWith("Mozilla")) { // covers I.E./Firefox/Safari/probably any other "advanced" Browser see http://en.wikipedia.org/wiki/User_agent + if (agent != null && agent.startsWith("Mozilla")) { // covers I.E./Firefox/Safari/probably any other "advanced" Browser see http://en.wikipedia.org/wiki/User_agent return LifeForm.CBLF; } return LifeForm.SBLF; // notably skips "curl","wget", (which is desired behavior. We don't want to try CSP, etc on these) @@ -165,17 +161,36 @@ public class HttpEpiTaf implements HttpTaf { public Resp revalidate(CachedPrincipal prin, Object state) { Resp resp; - for(HttpTaf taf : tafs) { - resp = taf.revalidate(prin,state); - switch(resp) { - case NOT_MINE: - break; - default: - return resp; + for (HttpTaf taf : tafs) { + resp = taf.revalidate(prin, state); + if (resp != Resp.NOT_MINE) { + return resp; } +// switch(resp) { +// case NOT_MINE: +// break; +// default: +// return resp; +// } } return Resp.NOT_MINE; } + + private void addToLog(List log, TafResp tresp) { + if (log == null) { + return; + } + log.add(tresp); + } + + private void printLog(List log) { + if (log == null) { + return; + } + for (TafResp tresp : log) { + access.log(Level.DEBUG, tresp.desc()); + } + } /** * List HttpTafs with their "toString" representations... primarily useful for Debugging in an IDE @@ -183,7 +198,7 @@ public class HttpEpiTaf implements HttpTaf { */ public String toString() { StringBuilder sb = new StringBuilder(); - for(HttpTaf ht : tafs) { + for (HttpTaf ht : tafs) { sb.append(ht.toString()); sb.append(". "); } diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/LoginPageTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/LoginPageTafResp.java index 9c9cbc22..3f80170e 100644 --- a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/LoginPageTafResp.java +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/LoginPageTafResp.java @@ -53,34 +53,42 @@ public class LoginPageTafResp extends AbsTafResp { return RESP.TRY_AUTHENTICATING; } - public static TafResp create(Access access, Locator locator, final HttpServletResponse resp, List redir) { - if(locator!=null) { - try { - Item item = locator.best(); - URI uri = locator.get(item); - if(uri!=null) { - StringBuilder sb = new StringBuilder(uri.toString()); - String query = uri.getQuery(); - boolean first = query==null || query.length()==0; - int count=0; - for(Redirectable t : redir) { - if(first) { - sb.append('?'); - first=false; - } - else sb.append('&'); - sb.append(t.get()); - ++count; - } - if(count>0)return new LoginPageTafResp(access, resp, sb.toString()); + public static TafResp create(Access access, Locator locator, final HttpServletResponse resp, List redirectables) { + if (locator == null) { + if (!redirectables.isEmpty()) { + access.log(Level.DEBUG,"LoginPage Locator is not configured. Taking first Redirectable Taf"); + return redirectables.get(0); + } + return NullTafResp.singleton(); + } + + try { + Item item = locator.best(); + URI uri = locator.get(item); + if (uri == null) { + return NullTafResp.singleton(); + } + + StringBuilder sb = new StringBuilder(uri.toString()); + String query = uri.getQuery(); + boolean first = ((query == null) || (query.length() == 0)); + for (Redirectable redir : redirectables) { + if (first) { + sb.append('?'); + first = false; + } + else { + sb.append('&'); } - } catch (Exception e) { - access.log(e, "Error deriving Login Page location"); + sb.append(redir.get()); } - } else if(!redir.isEmpty()) { - access.log(Level.DEBUG,"LoginPage Locator is not configured. Taking first Redirectable Taf"); - return redir.get(0); + if (!redirectables.isEmpty()) { + return new LoginPageTafResp(access, resp, sb.toString()); + } + } catch (Exception e) { + access.log(e, "Error deriving Login Page location"); } + return NullTafResp.singleton(); } } diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/taf/test/JU_HttpEpiTaf.java b/cadi/core/src/test/java/org/onap/aaf/cadi/taf/test/JU_HttpEpiTaf.java new file mode 100644 index 00000000..93a20474 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/taf/test/JU_HttpEpiTaf.java @@ -0,0 +1,145 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.taf.test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.Taf.LifeForm; +import org.onap.aaf.cadi.TrustChecker; +import org.onap.aaf.cadi.taf.HttpEpiTaf; +import org.onap.aaf.cadi.taf.HttpTaf; +import org.onap.aaf.cadi.taf.NullTaf; +import org.onap.aaf.cadi.taf.Redirectable; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; + +public class JU_HttpEpiTaf { + + private PropAccess access; + + @Mock private Locator locMock; + @Mock private TrustChecker trustCheckerMock; + @Mock private HttpServletRequest reqMock; + @Mock private HttpServletResponse respMock; + @Mock private HttpTaf tafMock; + @Mock private TafResp trespMock; + @Mock private Redirectable redirMock; + + @Before + public void setup() throws URISyntaxException { + MockitoAnnotations.initMocks(this); + + access = new PropAccess(new PrintStream(new ByteArrayOutputStream()), new String[0]); + } + + @Test + public void test() throws Exception { + HttpEpiTaf taf; + try { + taf = new HttpEpiTaf(access, locMock, trustCheckerMock); + fail("Should've thrown an exception"); + } catch (CadiException e) { + assertThat(e.getMessage(), is("Need at least one HttpTaf implementation in constructor")); + } + + taf = new HttpEpiTaf(access, locMock, trustCheckerMock, new NullTaf()); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + // Coverage of tricorderScan + taf.validate(LifeForm.LFN, reqMock, respMock); + when(reqMock.getHeader("User-Agent")).thenReturn("Non-mozilla-header"); + taf.validate(LifeForm.LFN, reqMock, respMock); + when(reqMock.getHeader("User-Agent")).thenReturn("Mozilla-header"); + taf.validate(LifeForm.LFN, reqMock, respMock); + + access.setLogLevel(Level.DEBUG); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + when(tafMock.validate(LifeForm.CBLF, reqMock, respMock)).thenReturn(trespMock); + when(trespMock.isAuthenticated()).thenReturn(RESP.TRY_ANOTHER_TAF); + taf = new HttpEpiTaf(access, locMock, trustCheckerMock, tafMock); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + when(trespMock.isAuthenticated()).thenReturn(RESP.IS_AUTHENTICATED); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + when(trespMock.isAuthenticated()).thenReturn(RESP.TRY_AUTHENTICATING); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + taf = new HttpEpiTaf(access, locMock, trustCheckerMock, tafMock, tafMock); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + when(tafMock.validate(LifeForm.CBLF, reqMock, respMock)).thenReturn(redirMock); + when(redirMock.isAuthenticated()).thenReturn(RESP.TRY_AUTHENTICATING); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + taf = new HttpEpiTaf(access, locMock, trustCheckerMock, tafMock, tafMock); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + taf = new HttpEpiTaf(access, locMock, trustCheckerMock, tafMock); + taf.validate(LifeForm.CBLF, reqMock, respMock); + + taf = new HttpEpiTaf(access, locMock, null, tafMock); + when(redirMock.isAuthenticated()).thenReturn(RESP.IS_AUTHENTICATED); + try { + taf.validate(LifeForm.CBLF, reqMock, respMock); + fail("Should've thrown an exception"); + } catch (Exception e) { + } + + assertThat(taf.revalidate(null), is(false)); + assertThat(taf.revalidate(null), is(false)); + + when(tafMock.revalidate(null, null)).thenReturn(Resp.NOT_MINE); + assertThat(taf.revalidate(null, null), is(Resp.NOT_MINE)); + when(tafMock.revalidate(null, null)).thenReturn(Resp.REVALIDATED); + assertThat(taf.revalidate(null, null), is(Resp.REVALIDATED)); + + when(tafMock.revalidate(null, null)).thenReturn(Resp.NOT_MINE).thenReturn(Resp.NOT_MINE).thenReturn(Resp.REVALIDATED); + taf = new HttpEpiTaf(access, locMock, trustCheckerMock, tafMock, tafMock, tafMock); + assertThat(taf.revalidate(null, null), is(Resp.REVALIDATED)); + + taf.toString(); + + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/taf/test/JU_LoginPageTafResp.java b/cadi/core/src/test/java/org/onap/aaf/cadi/taf/test/JU_LoginPageTafResp.java new file mode 100644 index 00000000..3124bbd4 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/taf/test/JU_LoginPageTafResp.java @@ -0,0 +1,101 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.taf.test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.Locator.Item; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.taf.LoginPageTafResp; +import org.onap.aaf.cadi.taf.Redirectable; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; + +public class JU_LoginPageTafResp { + + private static final String uriString = "example.com"; + + private URI uri; + private Access access; + private List redirectables; + + @Mock private HttpServletResponse respMock; + @Mock private Locator locatorMock; + @Mock private Redirectable redirMock; + + @Before + public void setup() throws URISyntaxException { + MockitoAnnotations.initMocks(this); + + access = new PropAccess(new PrintStream(new ByteArrayOutputStream()), new String[0]); + + redirectables = new ArrayList<>(); + uri = new URI(uriString); + } + + @Test + public void test() throws LocatorException, IOException { + TafResp resp; + resp = LoginPageTafResp.create(access, null, respMock, redirectables); + assertThat(resp.desc(), is("All Authentication denied")); + + redirectables.add(redirMock); + redirectables.add(redirMock); + resp = LoginPageTafResp.create(access, null, respMock, redirectables); + assertThat((Redirectable)resp, is(redirMock)); + + resp = LoginPageTafResp.create(access, locatorMock, respMock, redirectables); + assertThat(resp.desc(), is("All Authentication denied")); + + when(locatorMock.get((Item)any())).thenReturn(uri); + resp = LoginPageTafResp.create(access, locatorMock, respMock, redirectables); + assertThat(resp.desc(), is("Multiple Possible HTTP Logins available. Redirecting to Login Choice Page")); + assertThat(resp.authenticate(), is(RESP.HTTP_REDIRECT_INVOKED)); + assertThat(resp.isAuthenticated(), is(RESP.TRY_AUTHENTICATING)); + + redirectables = new ArrayList<>(); + resp = LoginPageTafResp.create(access, locatorMock, respMock, redirectables); + assertThat(resp.desc(), is("All Authentication denied")); + + } + +} -- 2.16.6