/* * Copyright 2012 The Solmix Project * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.gnu.org/licenses/ * or see the FSF site: http://www.fsf.org. */ package org.solmix.fmk.application; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.solmix.api.application.Application; import org.solmix.api.application.ApplicationManager; import org.solmix.api.application.ApplicationSecurity; import org.solmix.api.datasource.DSRequest; import org.solmix.api.datasource.DSRequestData; import org.solmix.api.datasource.DSResponse; import org.solmix.api.datasource.DSResponse.Status; import org.solmix.api.datasource.DataSource; import org.solmix.api.exception.SlxException; import org.solmix.api.jaxb.Eoperation; import org.solmix.api.jaxb.ToperationBinding; import org.solmix.api.jaxb.request.Roperation; import org.solmix.api.types.Texception; import org.solmix.api.types.Tmodule; import org.solmix.commons.collections.DataTypeMap; import org.solmix.commons.logs.SlxLog; import org.solmix.commons.util.DataUtils; import org.solmix.ds.context.Context; import org.solmix.fmk.datasource.DSResponseImpl; import org.solmix.fmk.datasource.DefaultDataSourceManager; import org.solmix.fmk.util.DataTools; /** * @author solmix.f@gmail.com * @since 0.0.1 * @version 110035 2010-12-20 solmix-ds */ @SuppressWarnings("unchecked") public class BuiltInApplication implements Application { public static final String P_AUTHENTICATION_ENABLED="authenticationEnabled"; public static final String P_AUTHORIZATION_ENABLED="authorizationEnabled"; protected boolean authorizationEnabled; protected boolean authenticationEnabled; /** * application ID. */ protected String appID = BUILT_IN_APPLICATION; private final static Logger log = LoggerFactory.getLogger(BuiltInApplication.class.getName()); /** * datasource request data which contains most of context for ds-request. */ private DSRequestData reqData; /** * application configuration for this unit. */ protected DataTypeMap appConfig; protected UserType definedUserTypes; /** * http servlet context. */ protected Context context; /** * operation id. */ protected String operation; protected String operationType; /** * DataSource request */ private DSRequest request; /** * DataSource response holder. */ private DSResponse result; /** * Cache operations */ protected Map<String, Roperation> operationsMap; /** * this application leased other datasource. */ Map<String, DataSource> leasedDataSources; private ApplicationSecurity security; private final String[] fmkDefinedOperations; public BuiltInApplication() { this(null); } /** * @param sc */ public BuiltInApplication(DataTypeMap config) { this.appConfig=config; leasedDataSources = new HashMap<String, DataSource>(2); authorizationEnabled=appConfig.getBoolean(P_AUTHORIZATION_ENABLED, false); authenticationEnabled=appConfig.getBoolean(P_AUTHENTICATION_ENABLED, false); String operations = appConfig.getString("definedOperations", "*"); fmkDefinedOperations = operations.split(","); } @Override public DataSource getDataSource(String dsName) throws SlxException { DataSource ds = leasedDataSources.get(dsName); if (ds == null) { ds = DefaultDataSourceManager.getDataSource(dsName); leasedDataSources.put(dsName, ds); } return ds; } /** * {@inheritDoc} * * @see org.solmix.api.application.Application#execute(org.solmix.api.datasource.DSRequest, java.lang.Object) */ @Override public DSResponse execute(DSRequest request, Context context) throws SlxException { this.request = request; this.context = context; reqData = request.getContext(); result = new DSResponseImpl(request != null ? request.getDataSource() : (DataSource) null, request); //check is authenticated if(this.authenticationEnabled){ if(this.security==null){ throw new java.lang.IllegalStateException("BuildIn application configured enable authentication,but the runtime ENV not have applicationSecurity instance"); }else{ if( !security.isAuthenticated()){ result.setStatus(Status.STATUS_LOGIN_REQUIRED); return result; } } } operation = reqData.getOperationId(); String dataSourceName = request.getDataSourceName(); if (operation == null) { Eoperation opType = reqData.getOperationType(); canPerformAutoOperation(opType); operation = DataTools.autoCreateOperationID(dataSourceName, opType); if (log.isDebugEnabled()) { log.debug("can not found the operationID ,auto created ID:[" + operation + "]"); MDC.put(SlxLog.LOG_CONTEXT, (new StringBuilder()) .append("#") .append(operation) .toString()); } } else { if (log.isTraceEnabled()) { MDC.put( SlxLog.LOG_CONTEXT, (new StringBuilder()) .append(dataSourceName.replace('/', '.')).append('#').append( operation).toString()); } } DSResponse dsresponse = null; /* * find the application's special configuration, if no find,use the auto operation. */ try { Roperation operationConfig = getOperationConfig(this.operation); if (operationConfig == null || operationConfig.getDataSource() == null) { log.trace("can not found operation configuration try to perform auto operation"); String _dsID = request.getDataSourceName(); Eoperation _opType = reqData.getOperationType(); canPerformAutoOperation(_opType); if (_dsID == null || _opType == null) { String __info = (new StringBuilder()).append("Auto-operation name (").append(operation).append(") must either be of the format ").append( "dataSourceId_operationType or a public zero-argument method").toString(); throw new SlxException(Tmodule.DATASOURCE, Texception.DS_NO_OPERATION_DEFINED, __info); } Map<String, Roperation> operations = getOperationsMap(); operationConfig = createAutoOperation(operationType, reqData.getRoperation(), request.getDataSourceName()); operations.put(this.operation, operationConfig); request.getContext().setRoperation(operationConfig); // request.getContext().getToperation() } else { this.operationType = operationConfig.getOperationType(); } // no permission if (!isPermitted(request, context)) { log.warn((new StringBuilder()).append("User does not qualify for any userTypes that are allowed to perform this operation ('").append( operation).append("')").toString()); result.setStatus(Status.STATUS_AUTHORIZATION_FAILURE); } else { executeAppOperation(); } if (result != null && result.getStatus() == Status.UNSET) { result.setStatus(Status.STATUS_SUCCESS); } // if ( !result.getContext().statusIsError() ) dsresponse = result; } finally { if (log.isTraceEnabled()) { MDC.remove(SlxLog.LOG_CONTEXT); } freeDataSources(); } return dsresponse; } protected Roperation getOperationConfig(String operation) { if (reqData.getRoperation() != null) return reqData.getRoperation(); Roperation opConfig = getOperationsMap().get(operation); if (opConfig == null) return new Roperation(); else return opConfig; } public Map<String, Roperation> getOperationsMap() { Object opmap = appConfig.get("operations"); if (opmap == null) appConfig.put("operations", new HashMap<Object, Roperation>(4)); if (operationsMap == null) operationsMap = ((Map<String, Roperation>) appConfig.get("operations")); return operationsMap; } public void freeDataSources() throws SlxException { if (leasedDataSources.values() != null) for (DataSource ds : leasedDataSources.values()) DefaultDataSourceManager.freeDataSource(ds); } protected void canPerformAutoOperation(Eoperation opType) throws SlxException { if (appID.equals(BUILT_IN_APPLICATION) || appID.equals(DEFAULT_APPLICATION)) if (definedUserTypes == UserType.ANONY_USER) { throw new SlxException(Tmodule.APP, Texception.SECURITY_DENIED, (new StringBuilder()).append("DENIED attempt to execute auto operation '").append(operation).append( "' bound to the auto-generated default application ").append("because authorization is currently enabled").toString()); } else { Boolean definedOperationsOnly = appConfig.getBoolean("definedOperationsOnly", false); Roperation opConfig = getOperationConfig(operation); if (definedOperationsOnly != null && definedOperationsOnly.booleanValue() && !containsOperationType(opType) && (opConfig == null || opConfig.getDataSource() == null)) throw new SlxException(Tmodule.APP, Texception.APP_CONFIG_DENIED, new StringBuilder().append( "DENIED attempt to execute auto operation '").append(operation).append("' bound to the application '").append(appID).append( "' because").append(" this application is configued for defined operations").append( " only and there is no definition for this operation").append(" in the app file").toString()); } } protected boolean containsOperationType(Eoperation opType) { if (opType == null) return false; if (fmkDefinedOperations[0].equals("*")) return true; for (String op : fmkDefinedOperations) { if (op.equals(opType.value())) { return true; } } return false; } protected Roperation createAutoOperation(String operationType, Roperation operation, String dataSourceName) { if (operation == null) { operation = new Roperation(); } operation.setDataSource(dataSourceName); operation.setOperationType(operationType); return operation; } private void executeAppOperation() throws SlxException { String _dsName = request.getDataSourceName(); if (_dsName == null || _dsName.isEmpty()) { String __info = (new StringBuilder()).append("No public zero-argument method named '_").append(operation).append( " and request does not specify a DataSource to use for a default operation").append(" - unable to proceed.").toString(); throw new SlxException(Tmodule.APP, Texception.APP_NO_DS_OR_OPERATION_DEFIEND, __info); } executeDefaultDSOperation(); } private void executeDefaultDSOperation() throws SlxException { String _dsName = request.getDataSourceName(); DataSource _ds = request.getDataSource(); if (_ds == null) { String __vinfo = (new StringBuilder()).append("Can't find dataSource: ").append(_dsName).append(" - please make sure that you have a ").append( _dsName).append(".ds.xml").append(" file for it in dsrepo").toString(); throw new SlxException(Tmodule.APP, Texception.DS_NO_FONUN_DATASOURCE, __vinfo); } Eoperation _operationType = request.getContext().getOperationType(); /** * Validation request. */ if (DataUtils.booleanValue(request.getContext().getIsClientRequest()) && (_operationType == Eoperation.REMOVE || _operationType == Eoperation.UPDATE)) { Boolean allowMultiUpdate = Boolean.FALSE; ToperationBinding _opBinding = _ds.getContext().getOperationBinding(_operationType, request.getContext().getOperationId()); if (_opBinding != null) allowMultiUpdate = _opBinding.isAllowMultiUpdate(); if (DataUtils.isNullOrEmpty(_ds.getContext().getPrimaryKeys()) && DataUtils.asBoolean(allowMultiUpdate)) { String __info = (new StringBuilder()).append(operationType).append(" operation received ").append("from client for DataSource '").append( _ds.getName()).append("', ").append("operationId '").append(request.getContext().getOperation()).append("'. This ").append( "is not allowed because the DataSource has no ").append("primaryKey. Either declare a primaryKey or ").append( "set allowMultiUpdate to true on the OperationBinding").toString(); throw new SlxException(Tmodule.APP, Texception.DS_UPDATE_WITHOUT_PK, __info); } } result = _ds.execute(request); } /* public boolean havePermission(DSRequest request, Context context) throws SlxException { check need authenticated or not if (!authenticationEnabled) return true; find user subject,make sure this thread is been authenticated Subject subject = null; try { subject = SecurityUtils.getSubject(); } catch (Exception e) { } no authentication return false if (subject == null || subject.isAuthenticated()) return false; if not require authorization,return true if (!authorizationEnabled) return true; if (userTypePermission()) { Eoperation opType = request.getContext().getOperationType(); if (definedOperationPermission(opType)) { DataSource ds = request.getDataSource(); if (ds == null) return true; List<String> roles = ds.getContext().getRequiresRoles(); String opId = request.getContext().getOperationId(); Tsecurity security = ds.getContext().getOperationBinding(opType, opId).getSecurity(); String binRoles = security == null ? null : security.getRequireRoles(); List<String> bRols; if (binRoles == null || binRoles.length() == 0) bRols = null; bRols = DataUtils.simpleSplit(binRoles, ";"); if (DataUtils.isNullOrEmpty(roles) && DataUtils.isNullOrEmpty(bRols)) return true; if (DataUtils.isNotNullAndEmpty(bRols)) { return haveRoles(subject, bRols); } else { return haveRoles(subject, roles); } } } return false; } protected DataTypeMap loadCustomConfig() { appConfig = new DataTypeMap(); // fmkDefinedOperations String operations = appConfig.getString("definedOperations", "*"); definedUserTypes = UserType.fromValue(appConfig.getInt("userType", 1)); fmkDefinedOperations = operations.split(","); adminUser = appConfig.getString("adminUser", "root"); return appConfig; } protected boolean userTypePermission() { if (definedUserTypes == null) { log.trace("No userTypes defined, allowing anyone access to all operations for this application"); return true; } switch (definedUserTypes) { case ANONY_USER: return true; case ADMIN_USER: { return Authentication.getUsername() == null ? false : Authentication.getUsername().equalsIgnoreCase(adminUser); } case AUTH_USER: { return Authentication.getUsername() == null ? false : true; } } return false; } protected boolean definedOperationPermission(Eoperation opType) { if (opType == null) return true; return this.containsOperationType(opType); } public boolean datasourcePermission(DataSource ds) { ds.getContext(); return false; } protected boolean haveRoles(Subject subject, List<String> roles) { boolean[] checkRoleResult = subject.hasRoles(roles); boolean havePermission = false; for (boolean r : checkRoleResult) { if (r) havePermission = r; } return havePermission; } protected boolean haveRequires(Subject subject, List<String> requires) { for (String r : requires) { if (!subject.isPermitted(r)) return false; } return true; } public boolean userQualifiesForType(String userType) { LoggerFactory.getLogger(SlxLog.AUTH_LOGNAME).info( "AppBase::boolean userQualifiesForType(String userType): override this method to provide custom userType qualification logic (base implementation returns true)"); return true; } protected boolean containsOperationType(Eoperation opType) { if (opType == null) return false; if (fmkDefinedOperations[0].equals("*")) return true; for (String op : fmkDefinedOperations) { if (op.equals(opType.value())) { return true; } } return false; } *//** * @param operation * @return *//* *//** * @throws SlxException *//* *//** * @throws SlxException *//* private void executeDefaultDSOperation() throws SlxException { String _dsName = request.getDataSourceName(); DataSource _ds = request.getDataSource(); if (_ds == null) { String __vinfo = (new StringBuilder()).append("Can't find dataSource: ").append(_dsName).append(" - please make sure that you have a ").append( _dsName).append(".ds.xml").append(" file for it in dsrepo").toString(); throw new SlxException(Tmodule.APP, Texception.DS_NO_FONUN_DATASOURCE, __vinfo); } Eoperation _operationType = request.getContext().getOperationType(); *//** * Validation request. *//* if (request.getContext().getIsClientRequest() && (_operationType == Eoperation.REMOVE || _operationType == Eoperation.UPDATE)) { Boolean allowMultiUpdate = Boolean.FALSE; ToperationBinding _opBinding = _ds.getContext().getOperationBinding(_operationType, request.getContext().getOperationId()); if (_opBinding != null) allowMultiUpdate = _opBinding.isAllowMultiUpdate(); if (DataUtils.isNullOrEmpty(_ds.getContext().getPrimaryKeys()) && DataUtils.asBoolean(allowMultiUpdate)) { String __info = (new StringBuilder()).append(operationType).append(" operation received ").append("from client for DataSource '").append( _ds.getName()).append("', ").append("operationId '").append(request.getContext().getOperation()).append("'. This ").append( "is not allowed because the DataSource has no ").append("primaryKey. Either declare a primaryKey or ").append( "set allowMultiUpdate to true on the OperationBinding").toString(); throw new SlxException(Tmodule.APP, Texception.DS_UPDATE_WITHOUT_PK, __info); } } result = _ds.execute(request); } */ /** * {@inheritDoc} * * @see org.solmix.api.application.Application#getServerID() */ @Override public String getServerID() { return ApplicationManager.BUILT_IN_APPLICATION; } /** * {@inheritDoc} * * @see org.solmix.api.application.Application#setApplicationSecurity(org.solmix.api.application.ApplicationSecurity) */ @Override public void setApplicationSecurity(ApplicationSecurity security) { this.security=security; } /** * {@inheritDoc} * * @see org.solmix.api.application.Application#isPermitted(org.solmix.api.datasource.DSRequest, org.solmix.ds.context.Context) */ @Override public boolean isPermitted(DSRequest request, Context context) throws SlxException { if(this.security!=null){ return security.isPermitted(request, context); } return true; } }