package org.atricore.idbus.capabilities.sso.ui.internal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.wicket.IRequestCycleProvider;
import org.apache.wicket.markup.html.IPackageResourceGuard;
import org.apache.wicket.markup.html.SecurePackageResourceGuard;
import org.apache.wicket.markup.html.pages.AccessDeniedPage;
import org.apache.wicket.markup.html.pages.PageExpiredErrorPage;
import org.apache.wicket.markup.parser.filter.RelativePathPrefixHandler;
import org.apache.wicket.markup.resolver.IComponentResolver;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.cycle.RequestCycleContext;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.atricore.idbus.capabilities.sso.ui.*;
import org.atricore.idbus.capabilities.sso.ui.agent.JossoAuthorizationStrategy;
import org.atricore.idbus.capabilities.sso.ui.resources.AppResourceLocator;
import org.atricore.idbus.capabilities.sso.ui.spi.*;
import org.atricore.idbus.kernel.main.mail.MailService;
import org.atricore.idbus.kernel.main.mediation.Channel;
import org.atricore.idbus.kernel.main.mediation.IdentityMediationUnit;
import org.atricore.idbus.kernel.main.mediation.IdentityMediationUnitRegistry;
import org.atricore.idbus.kernel.main.mediation.channel.IdPChannel;
import org.atricore.idbus.kernel.main.mediation.channel.SPChannel;
import org.atricore.idbus.kernel.main.mediation.provider.IdentityProvider;
import org.atricore.idbus.kernel.main.mediation.provider.ServiceProvider;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import java.io.Serializable;
import java.net.URL;
import java.util.*;
/**
* TODO : Implement a resource locator that can search for resources (pages, images, properties) in other bundles
*
* @author <a href=mailto:sgonzalez@atricore.org>Sebastian Gonzalez Oyuela</a>
*/
public abstract class BaseWebApplication extends WebApplication implements WebBrandingEventListener {
private static final Log logger = LogFactory.getLog(BaseWebApplication.class);
private static final Set<String> imageExtensions = new HashSet<String>();
private static final Set<String> fontExtensions = new HashSet<String>();
protected boolean ready;
// Dependency injection does not work for application objects (pax-wicket)!
protected BundleContext bundleContext;
protected ApplicationRegistry appConfigRegistry;
protected WebBrandingService brandingService;
protected MailService mailService;
protected WebBranding branding;
protected IdentityProvider identityProvider;
protected ServiceProvider selfServicesSP;
protected List<AppResource> appResources = new ArrayList<AppResource>();
protected IdentityMediationUnitRegistry idsuRegistry;
protected Set<PageMountPoint> mounts;
protected Map<String, PageMountPoint> mountsByPath;
static {
fontExtensions.add("ttf"); // TrueType font
fontExtensions.add("eot"); // Embedded OpenType font
imageExtensions.add("bmp"); // Bitmap Image File
imageExtensions.add("dds"); // DirectDraw Surface
imageExtensions.add("gif"); // Graphical Interchange Format File
imageExtensions.add("jpg"); // JPEG Image
imageExtensions.add("png"); // Portable Network Graphic
imageExtensions.add("psd"); // Adobe Photoshop Document
imageExtensions.add("pspimage"); // PaintShop Pro Image
imageExtensions.add("tga"); // Targa Graphic
imageExtensions.add("thm"); // Thumbnail Image File
imageExtensions.add("tif"); // Tagged Image File
imageExtensions.add("yuv"); // YUV Encoded Image File
imageExtensions.add("ico"); // Icon file
}
public BaseWebApplication() {
super();
}
public IdentityProvider getIdentityProvider() {
if (identityProvider == null)
resolveProviders();
return identityProvider;
}
public void setIdentityProvider(IdentityProvider identityProvider) {
this.identityProvider = identityProvider;
}
public ServiceProvider getSelfServicesSP() {
if (selfServicesSP == null)
resolveProviders();
return selfServicesSP;
}
public void setSelfServicesSP(ServiceProvider selfServicesSP) {
this.selfServicesSP = selfServicesSP;
}
public BundleContext getBundleContext() {
return bundleContext;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
public ApplicationRegistry getAppConfigRegistry() {
return appConfigRegistry;
}
public void setAppConfigRegistry(ApplicationRegistry appConfigRegistry) {
this.appConfigRegistry = appConfigRegistry;
}
public WebBrandingService getBrandingService() {
return brandingService;
}
public void setBrandingService(WebBrandingService brandingService) {
this.brandingService = brandingService;
}
public MailService getMailService() {
return mailService;
}
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
public boolean isReady() {
return ready;
}
@Override
protected void init() {
super.init();
preInit();
mountPages();
}
@Override
public void internalDestroy() {
super.internalDestroy();
if (brandingService != null) {
try {
brandingService.unregister(this);
} catch (Exception e) {
if (logger.isTraceEnabled())
logger.trace(e.getMessage(), e);
}
}
}
protected abstract void buildPageMounts();
protected PageMountPoint addPageMount(PageMountPoint m) {
if (mounts == null) {
mounts = new HashSet<PageMountPoint>();
mountsByPath = new HashMap<String, PageMountPoint>();
}
mounts.add(m);
mountsByPath.put(m.getPath(), m);
return m;
}
protected PageMountPoint addPageMount(String path, Class pageClass) {
return addPageMount(new PageMountPoint(path, pageClass));
}
protected void setupSettingPages() {
getApplicationSettings().setAccessDeniedPage(AccessDeniedPage.class);
getApplicationSettings().setPageExpiredErrorPage(PageExpiredErrorPage.class);
//getApplicationSettings().setInternalErrorPage(ApplicationErrorPage.class);
}
protected void mountPages() {
buildPageMounts();
for (PageMountPoint mount : mounts) {
mountPage(mount.getPath(), mount.getPageClass());
}
setupSettingPages();
}
public Class resolvePage(String path) {
PageMountPoint m = resolveMoutnPoint(path);
if (m == null) {
logger.warn("Page not found for " + path);
return null;
}
return m.getPageClass();
}
public PageMountPoint resolveMoutnPoint(String path) {
PageMountPoint m = mountsByPath.get(path);
if (m == null) {
logger.warn("page Mount point not found for " + path);
return null;
}
return m;
}
protected void preInit() {
setRequestCycleProvider(new IRequestCycleProvider()
{
public RequestCycle get(RequestCycleContext context)
{
return new IdBusRequestCycle(context);
}
});
//getRequestCycleSettings().addResponseFilter(new AjaxServerAndClientTimeFilter());
getDebugSettings().setAjaxDebugModeEnabled(false);
//Security settings
getSecuritySettings().setAuthorizationStrategy(new JossoAuthorizationStrategy());
// Resource settings
getResourceSettings().setEncodeJSessionId(false);
}
/**
* Injected services are available here
*/
protected void postConfig() {
// Markup settings
getMarkupSettings().setMarkupFactory(new IdBusMarkupParserFactory(getAppConfig()));
List<IComponentResolver> currentList = getPageSettings().getComponentResolvers();
List<IComponentResolver> newComponentsList = new ArrayList<IComponentResolver>(currentList.size());
// Alter prefix handling
for (IComponentResolver iComponentResolver : currentList) {
if (iComponentResolver instanceof RelativePathPrefixHandler) {
newComponentsList.add(new IdBusRelativePathPrefixHandler(getAppConfig().getMountPoint()));
} else {
newComponentsList.add(iComponentResolver);
}
}
// Page settings
getPageSettings().getComponentResolvers().clear();
getPageSettings().getComponentResolvers().addAll(newComponentsList);
if (branding.getAllowedResourcePatterns() != null && branding.getAllowedResourcePatterns().size() > 0) {
IPackageResourceGuard guard = this.getResourceSettings().getPackageResourceGuard();
if (guard instanceof SecurePackageResourceGuard) {
SecurePackageResourceGuard secureGuard = (SecurePackageResourceGuard) guard;
for (String pattern : branding.getAllowedResourcePatterns()) {
secureGuard.addPattern(pattern);
}
} else {
logger.error("Cannot add resource pattern to IPackageResourceGuard of type " + guard.getClass());
}
}
// Authn settings
// Do we have an IDP ? Resolve SSO endpoint
// Trigger automatic login
// Create security context, if available
// Session keep alive / validate (accessSession)
}
public WebBranding getBranding() {
return branding;
}
public WebAppConfig getAppConfig() {
if (!this.ready)
throw new IllegalStateException("Application has not been configured yet !");
if (appConfigRegistry == null)
throw new IllegalStateException("Application Configuration registry not found !");
WebAppConfig cfg = appConfigRegistry.lookupConfig(getApplicationKey());
if (cfg == null)
logger.error("No configuration found for Wicket application " + getApplicationKey());
return cfg;
}
public List<AppResource> getAppResources() {
return appResources;
}
public final synchronized void config(BundleContext bundleContext, ApplicationRegistry appConfigRegistry, WebBrandingService brandingService, IdentityMediationUnitRegistry idsuRegistry, MailService mailService) {
// We're ready
this.ready = true;
this.bundleContext = bundleContext;
this.appConfigRegistry = appConfigRegistry;
this.brandingService = brandingService;
this.idsuRegistry = idsuRegistry;
this.mailService = mailService;
// Register the application to the branding service
String brandingId = getAppConfig().getBrandingId();
branding = brandingService.lookup(brandingId);
if (branding != null) {
brandingService.register(this);
if (branding.getDefaultLocale() != null) {
logger.debug("Setting default locale to " + branding.getDefaultLocale());
Locale.setDefault(new Locale(branding.getDefaultLocale()));
}
} else {
logger.error("No branding configured for " + getAppConfig().getAppName() + " using ID : " + brandingId);
}
postConfig();
refreshBranding();
}
public void refreshBranding() {
Set<String> resourcePaths = new HashSet<String>();
// TODO : Instead of taking resources list from branding, also support scanning specific packages of specific bundles !!!!
if (branding != null) {
// TODO : Reset locale
// Mount branding shared resources explicitly declared
for (BrandingResource resource : branding.getResources()) {
// All shared resource MUST be scoped to AppResourceLocator
if (resource.isShared()) {
PackageResourceReference ref = new PackageResourceReference(AppResourceLocator.class, resource.getPath());
this.appResources.add(new AppResource(resource, ref));
mountResource("/" + resource.getPath(), ref);
resourcePaths.add(resource.getPath());
if (logger.isTraceEnabled())
logger.trace("Mounting EXPLICITY shared resource ["+resource.getId()+"] at /" + resource.getPath());
}
}
// Auto-discovery all resources bound to AppResourceLocator class package.
// Make them available as global resources
Bundle b = bundleContext.getBundle();
String basePath = "/" + AppResourceLocator.class.getPackage().getName().replace('.', '/');
Enumeration e = b.findEntries(basePath, "*", true);
while (e.hasMoreElements()) {
URL location = (URL) e.nextElement();
String path = location.getPath();
String mountPath = path.substring(basePath.length() + 1);
if (resourcePaths.contains(mountPath)) {
if (logger.isDebugEnabled())
logger.debug("Resource declared EXPLICITLY : "+ path);
continue;
}
BrandingResourceType type = getTypeFromPath(path);
if (type == null || type.equals(BrandingResourceType.OTHER))
continue;
String id = mountPath.replace('/', '-');
id = id.replace('.', '-');
if (logger.isTraceEnabled())
logger.trace("Mounting DISCOVERED shared resource ["+id+"] at /" + mountPath);
BrandingResource resource = new BrandingResource(id, mountPath, null, type);
PackageResourceReference ref = new PackageResourceReference(AppResourceLocator.class, resource.getPath());
this.appResources.add(new AppResource(resource, ref));
mountResource("/" + resource.getPath(), ref);
resourcePaths.add(resource.getPath());
if (logger.isTraceEnabled())
logger.trace("Mounting shared resource ["+resource.getId()+"] at /" + resource.getPath());
}
}
}
protected BrandingResourceType getTypeFromPath(String path) {
//String imgs
int mid = path.lastIndexOf('.');
if (mid < 0) return null;
String extension = path.substring(mid + 1, path.length());
if (extension.equalsIgnoreCase("css"))
return BrandingResourceType.CSS;
if (extension.equalsIgnoreCase("js"))
return BrandingResourceType.SCRIPT;
if (imageExtensions.contains(extension))
return BrandingResourceType.IMAGE;
if (fontExtensions.contains(extension))
return BrandingResourceType.FONT;
if (extension.equalsIgnoreCase("html"))
return BrandingResourceType.HTML;
return BrandingResourceType.OTHER;
}
public void removeBranding() {
// TODO : What happens when the branding is removed ?!
logger.warn("Configured branding was removed ! ["+branding.getId()+"]");
}
public void handleEvent(WebBrandingEvent event) {
// Not our branding
if (!branding.getId().equals(event.getBrandingId()))
return;
switch (event.getType()) {
case WebBrandingEvent.PUBLISH:
logger.debug("Processing branding event type PUBLISH : " + event.getType());
refreshBranding();
break;
case WebBrandingEvent.REMOVE:
logger.debug("Processing branding event type REMOVE : " + event.getType());
removeBranding();
break;
default:
logger.debug("Unknown branding event type " + event.getType());
break;
}
}
public class AppResource implements Serializable {
private BrandingResource resource;
private PackageResourceReference ref;
public AppResource(BrandingResource resource, PackageResourceReference ref) {
this.resource = resource;
this.ref = ref;
}
public BrandingResource getResource() {
return resource;
}
public PackageResourceReference getRef() {
return ref;
}
}
// This method must be invoked when the appliance is up and running
protected void resolveProviders() {
if (!ready)
throw new IllegalStateException("Application not configured yet !");
// Resolve identity provider
String unitName = getAppConfig().getUnitName();
String idpName = getAppConfig().getIdpName();
String ssSpName = getAppConfig().getSelfServicesSpName();
if (idpName == null)
logger.debug("IdP Name not provided for application " + getAppConfig().getAppName());
if (ssSpName == null)
logger.debug("IdP Name not provided for application " + getAppConfig().getAppName());
if(unitName != null) {
IdentityMediationUnit unit = idsuRegistry.lookupUnit(unitName);
for (Channel c : unit.getChannels()) {
// Look for the configured IDP, if any
if (idpName != null && c instanceof SPChannel) {
SPChannel spChannel = (SPChannel) c;
if (spChannel.getProvider().getName().equalsIgnoreCase(idpName))
identityProvider = (IdentityProvider) spChannel.getProvider();
} else if (ssSpName != null && c instanceof IdPChannel) {
IdPChannel idpChannel = (IdPChannel) c;
if (idpChannel.getProvider().getName().equalsIgnoreCase(ssSpName))
selfServicesSP = (ServiceProvider) idpChannel.getProvider();
}
}
if (idpName != null && identityProvider == null) {
logger.error("No IDP found with name " + idpName + " in Mediation Unit " + unitName);
}
if (ssSpName != null && selfServicesSP == null) {
logger.error("No SP found with name " + ssSpName + " in Mediation Unit " + unitName);
}
}
}
}