com.novartis.opensource.yada.plugin.Authorizer Maven / Gradle / Ivy
The newest version!
/**
*
*/
package com.novartis.opensource.yada.plugin;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import static com.kosprov.jargon2.api.Jargon2.*;
import com.novartis.opensource.yada.ConnectionFactory;
import com.novartis.opensource.yada.Finder;
import com.novartis.opensource.yada.YADAConnectionException;
import com.novartis.opensource.yada.YADARequest;
import com.novartis.opensource.yada.YADARequestException;
import com.novartis.opensource.yada.YADAResourceException;
import com.novartis.opensource.yada.YADASQLException;
import com.novartis.opensource.yada.YADASecurityException;
import com.novartis.opensource.yada.util.YADAUtils;
/**
* @author jfinn
* @since 8.7.6
*/
public class Authorizer extends AbstractPostprocessor implements Authorization {
/**
* @since 9.0.0
*/
private static Logger l = Logger.getLogger(Authorizer.class);
/**
* Contains resource and allowList
*/
private JSONObject yadaAuthorization = new JSONObject();
/**
* Contains the name of the resource we are authorizing
*
*/
private String resource = new String();
/**
* Contains the credentials of the user we are authorizing
*
*/
private String credentials = new String();
/**
* Contains the user identity data from authority
*/
private Object identity = new Object();
/**
* Contains the app to authorize
*/
private String app = new String();
/**
* Contains the locks to authorize
*
*/
private JSONObject locks = new JSONObject();
/**
* Contains the user grant from authority
*/
private Object grant = new Object();
/**
* Contains the query result from authority
*/
private String result = new String();
/**
* Contains the synchronization token
*/
private String syncToken = new String();
/**
* Contains the list of allow qualifiers from A11N
*/
private ArrayList allowList = new ArrayList();
/**
* Contains the list of deny qualifiers from A11N
*/
private ArrayList denyList = new ArrayList();
/**
* Constant with value {@value} referencing file containing credential hashes and grants.
*
* DO NOT COMMIT THIS FILE TO THE REPOSITORY. The
* {@code .shadow.json} file should not be renamed unless the {@code .gitignore}
* file at the root of {@code YADA_LIB} is updated to reflect the change.
*
* @since 9.0.0
*/
private static final String YADA_SHADOW = ".shadow.json";
/**
* Constant with value {@value} referencing the native user's password hash
* @since 9.0.0
*/
private static final String YADA_IDENTITY_HASH = "hash";
/**
* Constant with value {@value} referencing the native user's authority
* @since 9.0.0
*/
private static final String YADA_IDENTITY_GRANTS = "grants";
/**
* Constant with value {@value}. Default error.
* @since 9.0.0
*/
private static final String UNAUTHORIZED = "User is not authorized";
/**
* Sets additional request headers
*/
@Override
public String engage(YADARequest yadaReq, String result) throws YADAPluginException, YADASecurityException {
setResult(result);
this.setYADARequest(yadaReq);
this.setRequest(yadaReq.getRequest());
// Make header available
try
{
this.setHTTPHeaders(YADA_HDR_AUTH_NAMES);
}
catch (YADARequestException e)
{
throw new YADASecurityException(UNAUTHORIZED);
}
authorizeYADARequest(yadaReq, result);
return getResult();
}
/**
* Authorize payload
*/
@Override
public void authorize(String payload) throws YADASecurityException {
boolean authorized = false;
// Check request for locks, also sets app
// Other than setting the app, the locks *don't really matter*
// They don't matter at all for 9.0.0
if(Finder.hasYADALib())
{
YADARequest yr = this.getYADARequest();
List args = yr.getArgs();
if(null == args || args.isEmpty())
{
String qname = yr.getQname();
String app = qname.split("/")[0];
setApp(app);
}
else
{
setApp(args.get(0));
}
}
else
{
JSONObject requestedLocks = obtainLocks();
setLocks(requestedLocks);
if (hasLocks())
{
JSONArray key = getLocks().names();
for (int i = 0; i < key.length(); ++i)
{
String grant = key.getString(i);
String listtype = getLocks().getString(grant);
if (listtype.equals(AUTH_TYPE_WHITELIST))
{
// Add locks to allowList
addAllowListEntry(grant);
}
}
}
}
// Check cache for identity
Object ident = obtainIdentity();
setIdentity(ident);
if (hasIdentity())
{
String app = this.getApp();
if(app.equals(Finder.YADA))
{
authorized = true;
}
else
{
// Obtain a relevant GRANT if it exists within IDENTITY
Object grant = obtainGrant(app);
setGrant(grant);
// Is there a GRANT for this APP
if (hasGrants()) // even without locks, a grant for the app will pass
{
authorized = true;
}
}
}
if (authorized == true)
{
// Return the tokens
setResult(generateResult().toString());
}
else
{
throw new YADASecurityException(UNAUTHORIZED);
}
}
/**
* @return a {@link JSONObject} containing the {@link Authorization#YADA_HDR_SYNC_TKN}
* and {@link Authorization#YADA_HDR_AUTH_JWT_PREFIX} values
*/
public JSONObject generateResult() {
JSONObject result = new JSONObject();
// Add the sync token
result.put(YADA_HDR_SYNC_TKN, getSyncToken());
// Add the identity token
result.put(YADA_HDR_AUTH_JWT_PREFIX, this.getToken());
return result;
}
/**
*
* @return yadaauth {Role: [whitelist/blacklist]}
*/
public JSONObject obtainLocks() {
JSONObject result = new JSONObject();
// The first argument is the APP (required) and set
// All following are the LOCK(S) (optional) and returned if present
// This method sets whitelist values only
List arg = this.getYADARequest().getArgs();
if (arg != null && !arg.isEmpty())
{
for (int i = 0; i < arg.size(); i++)
{
if (i == 0)
{
this.setApp(arg.get(i));
}
else
{
result.put(arg.get(i), AUTH_TYPE_WHITELIST);
}
}
}
return result;
}
/**
* Check header for credentials, obtain identity, obtain token, and cache
* identity with token
*
* @throws YADASecurityException when token acquisition goes awry
*/
@Override
public void obtainToken(YADARequest yadaReq) throws YADASecurityException {
// Check header for credentials
Pattern rxAuthUsr = Pattern.compile(RX_HDR_AUTH_USR_PREFIX);
boolean obtainedtoken = false;
if (this.hasHttpHeaders())
{
for (int i = 0; i < this.getHttpHeaders().names().length(); i++)
{
// Check for basic auth
Matcher m1 = rxAuthUsr
.matcher((CharSequence) this.getHttpHeaders().get(this.getHttpHeaders().names().getString(i)));
if (m1.matches() && m1.groupCount() == 3)
{// valid header
setCredentials(m1.group(3));
break;
}
}
}
if (hasCredentials())
{
// We have credentials
byte[] credentialBytes = Base64.getDecoder().decode(getCredentials());
String credentialString = new String(credentialBytes);
String userid = new String();
String pw = new String();
Pattern rxAuthUsrCreds = Pattern.compile(RX_HDR_AUTH_USR_CREDS);
Matcher m2 = rxAuthUsrCreds.matcher(credentialString);
if (m2.matches())
{ // found user
userid = m2.group(1);
pw = m2.group(2);
}
try
{
// create the sync token
generateSyncToken(userid);
// use credentials to retrieve user identity
String id = obtainIdentity(userid, pw).toString();
if (id.length() > 0)
{
// create the identity token
generateToken(userid);
if (hasToken())
{
// add identity to cache using token as key
this.setCacheEntry(YADA_IDENTITY_CACHE, (String) this.getToken(), id, YADA_IDENTITY_TTL);
obtainedtoken = true;
}
}
}
catch (YADASecurityException e)
{
throw new YADASecurityException(UNAUTHORIZED);
}
catch (YADAResourceException e)
{
String msg = "Unable to load credenitial store";
throw new YADASecurityException(msg, e);
}
}
if (obtainedtoken == false)
{
throw new YADASecurityException(UNAUTHORIZED);
}
}
/**
* @param userid the username of the attempted logged user
* @throws YADASecurityException when token generation goes awry, usually due to invalid or missing JWT input arguments
*/
public void generateToken(String userid) throws YADASecurityException {
// issueDate: JWT iat
// expirationDate: issueDate + identity cache TTL seconds
String token;
Instant issueDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);
Instant expirationDate = issueDate.plus(YADA_IDENTITY_TTL, ChronoUnit.SECONDS);
try
{
token = JWT.create().withSubject(userid).withExpiresAt(Date.from(expirationDate))
.withIssuer(System.getProperty(JWTISS)).withIssuedAt(Date.from(issueDate))
.sign(Algorithm.HMAC512(System.getProperty(JWSKEY)));
}
catch (IllegalArgumentException | JWTCreationException e)
{
throw new YADASecurityException(UNAUTHORIZED);
}
this.setToken(token);
}
/**
* @param userid the username of the attempted logged user
*/
public void generateSyncToken(String userid) {
// Create a synchronization token with the userid and the current time
Instant issueDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);
String token = userid + issueDate.toString();
token = String.valueOf(token.hashCode());
setSyncToken(token);
}
/**
* Overrides {@link TokenValidator#validateToken()}. Validates JWT.
*/
@Override
public void validateToken() throws YADASecurityException {
if (hasToken())
{
// validate token as well-formed
try
{
JWT.require(Algorithm.HMAC512(System.getProperty(JWSKEY))).withIssuer(System.getProperty(JWTISS)).build()
.verify((String) this.getToken());
}
catch (JWTVerificationException | IllegalArgumentException exception)
{
// UTF-8 encoding not supported
String msg = "Validation Error ";
throw new YADASecurityException(msg, exception);
}
}
}
/**
* Obtain Identity with basic authentication
*
* @param userid the username of the attempted logged user
* @param pw the password of the attempted logged user
*
* @return identity
* @throws YADASecurityException when the credentials are invalid
* @throws YADAResourceException when the credential store exists but can't be loaded
*
*/
public Object obtainIdentity(String userid, String pw) throws YADASecurityException, YADAResourceException {
JSONObject result = new JSONObject();
JSONArray a = new JSONArray();
ArrayList al = new ArrayList();
JSONArray aid = new JSONArray();
// TODO: Replace prepared statement with YADA query
if (Finder.hasYADALib())
{
// Steps to build id:
// execute query to obtain app, role for user_id
String shadowFile = "";
try
{
shadowFile = Finder.getEnv("YADA.shadow");
}
catch (YADAResourceException e)
{
l.info("[YADA.shadow] property not set. Using default.");
shadowFile = YADA_SHADOW;
}
String shadowPath = String.format("%s/%s", Finder.getYADALib(), shadowFile);
String shadow;
try
{
Set filePerm = null;
try
{
filePerm = Files.getPosixFilePermissions(Paths.get(shadowPath));
}
catch (IOException e)
{
String msg = "Unable to retrieve permissions of shadow file";
throw new YADAResourceException(msg, e);
}
String permission = PosixFilePermissions.toString(filePerm);
if(!permission.contentEquals("rw-------"))
{
String msg = "Unable to proceed with authorization. Shadow file permissions must match 600";
throw new YADASecurityException(msg);
}
shadow = new String(Files.readAllBytes(Paths.get(shadowPath)), StandardCharsets.UTF_8);
JSONObject jshadow = new JSONObject(shadow);
JSONObject identity = jshadow.getJSONObject("users").getJSONObject(userid);
String hash = identity.getString(YADA_IDENTITY_HASH);
Verifier verifier = jargon2Verifier();
boolean matches = verifier.hash(hash).password(pw.getBytes()).verifyEncoded();
if(!matches)
{
String msg = "Unable to proceed with authorization. Check credentials.";
throw new YADASecurityException(msg);
}
JSONArray grants = identity.getJSONArray(YADA_IDENTITY_GRANTS); //{"app":app,"keys":["","",""]}
JSONArray keys = new JSONArray();
for(int i=0; i 0)
{
// Distill al to applist by removing any duplicate apps
ArrayList applist = new ArrayList();
for (String app: al)
{
if (!applist.contains(app))
{
applist.add(app);
}
}
// assemble identity object
for (int i = 0; i < applist.size(); i++)
{
JSONArray keys = new JSONArray();
for (int ii = 0; ii < a.length(); ii++)
{
if (applist.get(i).equals(a.getJSONObject(ii).getString(YADA_IDENTITY_APP).toString()))
{
// {YADA_IDENTITY_KEY,
// a.getJSONObject(i).getString(YADA_IDENTITY_KEY)}
keys.put(new JSONObject().put(YADA_IDENTITY_KEY, a.getJSONObject(i).getString(YADA_IDENTITY_KEY)));
}
}
aid.put(new JSONObject().put(YADA_IDENTITY_APP, applist.get(i)).put(YADA_IDENTITY_KEYS, keys));
}
}
}
catch (SQLException | YADAConnectionException | YADASQLException e)
{
String msg = "Unauthorized.";
throw new YADASecurityException(msg, e);
}
}
result.put(YADA_IDENTITY_SUB, userid);
// Use sync token in Gatekeeper to verify the sender owns the token
result.put(YADA_HDR_SYNC_TKN, getSyncToken());
// a is used to get the form:
// [{"app":app1,"grant":grant1},{"app":app1,"grant":grant2},...{"app":appN,"grant":grantN}]
// result.put(YADA_IDENTITY_GRANTS, a);
// aid is used to get the form:
// [{"app":app1,"keys":[{"key":key1},{"key":key2},...{"key":keyN}]}]
result.put(YADA_IDENTITY_GRANTS, aid);
result.put(YADA_IDENTITY_IAT, java.time.Instant.now().getEpochSecond());
// setIdentity(result);
return result.toString();
}
/**
* @return the {@link #identity} object from the {@link Authorization#YADA_IDENTITY_CACHE}
*/
public Object obtainIdentity() {
Object result = getCacheEntry(YADA_IDENTITY_CACHE, (String) this.getToken());
return result;
}
/**
* Obtain specified GRANT(KEYS) from current identity
*
* @param app the app to which the desired grants to check are mapped (usu. the current app)
* @return a {@link JSONArray} as an {@link Object} containing the {@link Authorization#YADA_IDENTITY_KEYS}
*/
public Object obtainGrant(String app) {
JSONObject identity = new JSONObject((String) getIdentity());
JSONArray identGrants = identity.getJSONArray(YADA_IDENTITY_GRANTS);
// find the app
JSONArray keys = new JSONArray();
for (int i = 0; i < identGrants.length(); i++)
{
String identGrantApp = identGrants.getJSONObject(i).getString(YADA_IDENTITY_APP).toString();
if (app.equals(identGrantApp))
{
JSONArray identGrantKeys = identGrants.getJSONObject(i).getJSONArray(YADA_IDENTITY_KEYS);
for (int ii = 0; ii < identGrantKeys.length(); ii++)
{
keys.put(identGrantKeys.getJSONObject(ii).getString(YADA_IDENTITY_KEY));
}
}
}
return keys;
}
/**
* @return {@code true} if {@link AbstractPostprocessor#getToken()} is set,
* otherwise {@code false}
*/
public boolean hasToken() {
if (null != this.getToken() && !"".equals(this.getToken()))
{
return true;
}
return false;
}
/**
* @return {@code true} if {@link #locks} has at least 1 entry, otherwise
* {@code false}
*/
public boolean hasLocks() {
if (getLocks().length() > 0)
{
return true;
}
return false;
}
/**
* @return {@code true} if {@link #grant} has at least 1 entry, otherwise
* {@code false}
*/
public boolean hasGrants() {
if (((JSONArray) getGrant()).length() > 0)
{
return true;
}
return false;
}
/**
* @return {@code true} if the {@link #identity} is a non-null, non-empty {@link String}
*/
public boolean hasIdentity() {
if (null != getIdentity() && !"".equals(getIdentity()))
{
return true;
}
return false;
}
/**
* @return {@code true} if {@link #allowList} has at least 1 entry, otherwise
* {@code false}
*/
public boolean hasAllowList() {
if (getAllowList().size() > 0)
{
return true;
}
return false;
}
/**
* @return {@code true} if {@link #credentials} has at least 1 entry, otherwise
* {@code false}
*/
public boolean hasCredentials() {
if (null != getCredentials() && !"".equals(getCredentials()))
{
return true;
}
return false;
}
/**
* @return {@link #yadaAuthorization}
*/
public JSONObject getYADAAuthorization() {
return this.yadaAuthorization;
}
/**
* @param yadaAuthorization the yadaAuthorization to set
*/
public void setYADAAuthorization(JSONObject yadaAuthorization) {
this.yadaAuthorization = yadaAuthorization;
}
/**
* @return {@link #resource}
*/
public String getResource() {
return this.resource;
}
/**
* @param resource the resource to set
*/
public void setResource(String resource) {
this.resource = resource;
}
/**
* @return {@link #credentials}
*/
public String getCredentials() {
return this.credentials;
}
/**
* @param credentials the credentials to set
*/
public void setCredentials(String credentials) {
this.credentials = credentials;
}
/**
* @return {@link #grant}
*/
public Object getGrant() {
return this.grant;
}
/**
* @param grant the grant to set
*/
public void setGrant(Object grant) {
this.grant = grant;
}
/**
* @return {@link #identity}
*/
public Object getIdentity() {
return this.identity;
}
/**
* @param identity the identity to set
*/
public void setIdentity(Object identity) {
this.identity = identity;
}
/**
* @return {@link #locks}
*/
public JSONObject getLocks() {
return this.locks;
}
/**
* @param locks the locks to set
*/
public void setLocks(JSONObject locks) {
this.locks = locks;
}
/**
* @return {@link #allowList}
*/
public ArrayList getAllowList() {
return this.allowList;
}
/**
* @param grant the privilege to allow
*/
public void addAllowListEntry(String grant) {
this.allowList.add(grant);
}
/**
* @param grant the privilege to deny
*/
public void removeAllowListEntry(String grant) {
this.allowList.remove(grant);
}
/**
* @return the {@link #denyList}
*/
public ArrayList getDenyList() {
return this.denyList;
}
/**
* @param grant the privilege to deny
*/
public void addDenyListEntry(String grant) {
this.denyList.add(grant);
}
/**
* @return the {@link #app}
*/
public String getApp() {
return this.app;
}
/**
* @param app the app to set
*/
public void setApp(String app) {
this.app = app;
}
/**
* @return {@link #result}
*/
public String getResult() {
return this.result;
}
/**
* @param result the result to set
*/
public void setResult(String result) {
this.result = result;
}
/**
* @return {@link #syncToken}
*/
public String getSyncToken() {
return this.syncToken;
}
/**
* @param syncToken the syncToken to set
*/
public void setSyncToken(String syncToken) {
this.syncToken = syncToken;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy