// Copyright (c) 2014, SAS Institute Inc., Cary, NC, USA, All Rights Reserved package com.sas.unravl.auth; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.sas.unravl.ApiCall; import com.sas.unravl.UnRAVL; import com.sas.unravl.UnRAVLException; import com.sas.unravl.annotations.UnRAVLAuthPlugin; import com.sas.unravl.assertions.UnRAVLAssertionException; import com.sas.unravl.generators.Text; import com.sas.unravl.util.Json; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import org.apache.commons.codec.binary.Base64; import org.apache.http.message.BasicHeader; import org.apache.log4j.Logger; /** * An auth element which provides basic authentication. This authenticates by * using the user's credentials stored in their ~/.netrc or %USERPROFILE%\_netrc * file (Windows) or in "login" and "password" elements of the script. * <p> * This precondition is specified via * * <pre> * "auth" : { "basic" : true } * "auth" : { "basic" : true, "login" : "myuserid" } * "auth" : { "basic" : true, "login" : "userid", "password" : "mySecret" } * "auth" : { "basic" : true, "mock" : "false" } * </pre> * <p> * This adds an <code>Authentication:Basic <em>encoded-credentials</em></code> * header to the request. The <code><em>encoded-credentials</em></code> is the * Base64 encoding of the username:password. The encoded credentials are masked * when logging the request headers. * </p> * <p> * If mock is true, present mock authentication credentials * <p> * TODO: allow an alternate location so credentials can be shared across * hosts/domains instead of having to have a .netrc file for each host * </p> * * <pre> * "auth" : { "basic" : "hostname" } * </pre> * * @author DavidBiesack@sas.com */ @UnRAVLAuthPlugin("basic") public class BasicAuth extends BaseUnRAVLAuth { private static final Logger logger = Logger.getLogger(BasicAuth.class); private boolean mock; // JSON spec contains "mock" : true, then mock out the // responses @Override public void authenticate(UnRAVL script, ObjectNode scriptlet, ApiCall call) throws UnRAVLAssertionException, UnRAVLException { super.authenticate(script, scriptlet, call); authenticateAndAddAuthenticationHeader(scriptlet); } void authenticateAndAddAuthenticationHeader(ObjectNode auth) throws UnRAVLException { try { // TODO: generalize this check and push it into BaseUnRAVLAuth // It should also use a getEffectiveURI() and getEffectiveMethod() // which walk templates in case the script does not have them. if (getScript().getURI() == null || getScript().getMethod() == null) throw new UnRAVLException( "basic auth requires an HTTP method and URI"); // TODO: allow the value to be a string; use that host name or // location for credential lookup JsonNode authVal = Json.firstFieldValue(getScriptlet()); if (!authVal.isBoolean()) { throw new UnRAVLException( "Value of \"auth\" must be boolean. Found: " + authVal); } if (!authVal.booleanValue()) return; if (getScriptlet().get("mock") != null) mock = getScriptlet().get("mock").booleanValue(); // Note: the URI should already be expanded at this point String location = getCall().getURI(); URI uri = new URI(location); basicAuth(uri, auth); } catch (URISyntaxException e) { new UnRAVLException(e.getMessage(), e); } catch (IOException e) { new UnRAVLException(e.getMessage(), e); } } private void basicAuth(URI uri, ObjectNode auth) throws UnRAVLException, IOException { String host = uri.getHost(); CredentialsProvider cp = getScript().getRuntime().getPlugins() .getCredentialsProvider(); cp.setRuntime(getScript().getRuntime()); HostCredentials credentials = cp.getHostCredentials(host, auth, mock); if (credentials == null) throw new UnRAVLException("No Basic Auth credentials for host " + host); String creds = new Base64().encodeToString(Text.utf8(credentials .getUserName() + ":" + credentials.getPassword())); credentials.clear(); // TODO: use the ApiCall and add headers there instead of mutating the // script getScript().addRequestHeader( new BasicHeader("Authorization", "Basic " + creds)); logger.info("\"basic\" auth added 'Authorization: Basic ********' header"); } }