
io.continual.http.app.servers.endpoints.TypicalRestApiEndpoint Maven / Gradle / Ivy
package io.continual.http.app.servers.endpoints;
import java.io.IOException;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.continual.builder.Builder.BuildFailure;
import io.continual.http.app.servers.CorsOptionsRouter;
import io.continual.http.service.framework.context.CHttpRequestContext;
import io.continual.iam.IamService;
import io.continual.iam.access.AccessDb;
import io.continual.iam.access.Resource;
import io.continual.iam.exceptions.IamSvcException;
import io.continual.iam.identity.Identity;
import io.continual.iam.identity.UserContext;
import io.continual.services.ServiceContainer;
import io.continual.util.standards.HttpStatusCodes;
public class TypicalRestApiEndpoint extends JsonIoEndpoint
{
public static final String kSetting_ContinualProductTag = "apiKeyProductTag";
public static final String kContinualProductTag = "continual";
public static final String kContinualSystemsGroup = "continualSystems";
public static final String kSetting_AuthLineHeader = "http.auth.header";
public static final String kSetting_DateLineHeader = "http.date.header";
public static final String kSetting_MagicLineHeader = "http.magic.header";
public static final String kDefault_AuthLineHeader = "X-Continual-Auth";
public static final String kDefault_DateLineHeader = "X-Continual-Date";
public static final String kDefault_MagicLineHeader = "X-Continual-Magic";
public interface Authenticator
{
I authenticate ( IamService am, CHttpRequestContext context ) throws IamSvcException;
}
/**
* An API handler that's provided the context and an authenticated user.
*
* @param
*/
public interface ApiHandler
{
/**
* Handle the request as the given user
*
* @param context the request context
* @param uc the user context
* @throws IOException
*/
void handle ( CHttpRequestContext context, UserContext uc ) throws IOException;
}
/**
* Make a simple resource reference from the given name
* @param named
* @return a resource
*/
public static Resource makeResource ( String named ) { return Resource.fromName ( named ); }
/**
* A resource access statement used to express a resource and operation that a user must be allowed.
*/
public static class ResourceAccess
{
public ResourceAccess ( String resourceId, String operation )
{
fResource = Resource.fromName ( resourceId );
fOp = operation;
}
public final Resource fResource;
public final String fOp;
}
/**
* Construct the base endpoint with a service container and specific settings
* @param sc
* @param settings
* @throws BuildFailure
*/
@SuppressWarnings("unchecked")
public TypicalRestApiEndpoint ( ServiceContainer sc, JSONObject settings ) throws BuildFailure
{
fAccts = sc.getReqd ( getAcctsSvcName ( settings ), IamService.class );
fAuthenticator = new AuthList ( settings );
}
/**
* Handle the given request with the given ApiHandler after authentication
* @param context
* @param handler
*/
public void handleWithApiAuth ( CHttpRequestContext context, ApiHandler handler )
{
handleWithApiAuthAndAccess ( context, handler );
}
/**
* Handle the given request with the given ApiHandler after authentication and access control checks.
* @param context
* @param handler
* @param accessReqd
*/
public void handleWithApiAuthAndAccess ( CHttpRequestContext context, ApiHandler handler, ResourceAccess... accessReqd )
{
try
{
// add cors headers
CorsOptionsRouter.setupCorsHeaders ( context );
// get the user
final UserContext user = getUser ( context );
if ( user == null )
{
sendNotAuth ( context );
return;
}
// check for required access
final String uid = user.getEffectiveUserId ();
final AccessDb> adb = fAccts.getAccessDb ();
for ( ResourceAccess ra : accessReqd )
{
if ( !adb.canUser ( uid, ra.fResource, ra.fOp ) )
{
sendNotAuth ( context );
return;
}
}
// access allowed, call the handler
handler.handle ( context, user );
}
catch ( IOException e )
{
log.warn ( e.getMessage () );
sendStatusCodeAndMessage ( context, HttpStatusCodes.k500_internalServerError, "I/O problem writing the response, but... you got it???" );
}
catch ( JSONException | IamSvcException e )
{
log.warn ( e.getMessage (), e );
sendStatusCodeAndMessage ( context, HttpStatusCodes.k500_internalServerError, "There was a problem handling your API request." );
}
}
/**
* Can the given user perform the given operation on the given resource?
* @param context
* @param user
* @param resId
* @param operation
* @return true if the user is permitted
*/
public boolean canUser ( CHttpRequestContext context, UserContext user, String resId, String operation ) throws IamSvcException
{
final boolean result = fAccts.getAccessDb ().canUser ( user.getEffectiveUserId (), makeResource ( resId ), operation );
if ( !result )
{
log.info ( user.toString () + " cannot " + operation + " object " + resId );
}
return result;
}
/**
* Get the current user via authentication and return a user context
* @param context
* @return a UserContext or null of the user is not authenticated
* @throws IamSvcException
*/
public UserContext getUser ( final CHttpRequestContext context ) throws IamSvcException
{
final IamService am = fAccts;
UserContext result = null;
I authUser = null;
try
{
// get this user authenticated
authUser = fAuthenticator.authenticate ( am, context );
// if we have an authentic user, build a user context
if ( authUser != null )
{
final String authFor = context.request ().getFirstHeader ( "X-AuthFor" );
if ( authFor != null && authFor.length () > 0 && !authFor.equals ( authUser.getId () ) )
{
// authorized user is vouching for another...
// get that user's identity
final I authForUser = am.getIdentityDb ().loadUser ( authFor );
// if the user exists and this user is authorized...
if ( authForUser != null && authUser.getGroup ( kContinualSystemsGroup ) != null )
{
// auth user is part of the special systems group
result = new UserContext.Builder ()
.forUser ( authForUser )
.sponsoredByUser ( authUser )
.build ()
;
}
// else: this is a bogus request
}
else // no auth-for or it's the same user
{
result = new UserContext.Builder ()
.forUser ( authUser )
.build ()
;
}
}
return result;
}
catch ( IamSvcException x )
{
log.warn ( "Error processing authentication: " + x.getMessage () );
throw x;
}
}
protected IamService getInternalAccts ()
{
return fAccts;
}
private final IamService fAccts;
private final Authenticator fAuthenticator;
private static final Logger log = LoggerFactory.getLogger ( TypicalRestApiEndpoint.class );
// configs have used different conventions before the lookup was centralized heres
public static String getAcctsSvcName ( JSONObject settings )
{
for ( String key : kAcctSvcKeys )
{
final String acctSvcName = settings.optString ( key, null );
if ( acctSvcName != null )
{
return acctSvcName;
}
}
return kAcctSvcKeys[0];
}
private static String[] kAcctSvcKeys = new String[] { "accounts", "accountsService" };
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy