package act.app.conf; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import act.app.event.AppEventId; import act.conf.AppConfig; import act.security.CSRFProtector; import org.osgl.$; import org.osgl.http.H; import org.osgl.logging.Logger; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import org.osgl.util.StringValueResolver; import java.util.*; /** * Base class for app developer implement source code based configuration */ public abstract class AppConfigurator<T extends AppConfigurator> extends AppConfig<T> { protected static Logger logger = AppConfig.logger; protected static final H.Method GET = H.Method.GET; protected static final H.Method POST = H.Method.POST; protected static final H.Method PUT = H.Method.PUT; protected static final H.Method DELETE = H.Method.DELETE; private transient Set<String> controllerClasses = C.newSet(); private Map<String, Object> userProps = C.newMap(); @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null) { return false; } return obj.getClass() == getClass(); } @Override public int hashCode() { return getClass().hashCode(); } @Override public String toString() { return getClass().getName(); } @Override protected void releaseResources() { controllerClasses.clear(); userProps.clear(); releaseAppConfigResources(); super.releaseResources(); } protected T registerStringValueResolver(Class<T> targetType, StringValueResolver<T> resolver) { app().resolverManager().register(targetType, resolver); return me(); } protected CorsSetting cors() { return new CorsSetting(this); } protected CsrfSetting csrf() { return new CsrfSetting(this); } public void onRouteAdded(String controllerClassName) { controllerClasses.add(controllerClassName); } public Set<String> controllerClasses() { return C.newSet(controllerClasses); } protected T prop(String key, Object val) { userProps.put(key, val); return me(); } public Set<String> propKeys() { return userProps.keySet(); } public <V> V propVal(String key) { return $.cast(userProps.get(key)); } /** * Sub class shall override this method to do the configuration */ public abstract void configure(); protected void releaseAppConfigResources() {} protected static class CsrfSetting { private AppConfigurator conf; private boolean enabled; private String headerName; private String paramName; private String cookieName; private CSRFProtector protector; CsrfSetting(AppConfigurator conf) { this.conf = conf; this.enabled = true; conf.app().jobManager().on(AppEventId.CONFIG_PREMERGE, new Runnable() { @Override public void run() { checkAndCommit(); } }); } public CsrfSetting enable() { enabled = true; return this; } public CsrfSetting disable() { enabled = false; return this; } public CsrfSetting headerName(String name) { this.headerName = name; return this; } public CsrfSetting paramName(String name) { this.paramName = name; return this; } public CsrfSetting cookieName(String name) { this.cookieName = name; return this; } public CsrfSetting protector(CSRFProtector protector) { this.protector = $.notNull(protector); return this; } private void checkAndCommit() { if (!enabled) { logger.info("Global CSRF is disabled"); conf.enableCsrf(false); } logger.info("Global CSRF is enabled"); conf.csrfCookieName(this.cookieName); conf.csrfHeaderName(this.headerName); conf.csrfParamName(this.paramName); conf.csrfProtector(this.protector); } } protected static class CorsSetting { private AppConfigurator conf; private boolean enabled; private String allowOrigin; private int maxAge; private List<String> headersBoth = new ArrayList<String>(); private List<String> headersAllowed = new ArrayList<String>(); private List<String> headersExpose = new ArrayList<String>(); CorsSetting(AppConfigurator conf) { this.conf = conf; this.enabled = true; conf.app().jobManager().on(AppEventId.CONFIG_PREMERGE, new Runnable() { @Override public void run() { checkAndCommit(); } }); } public CorsSetting enable() { enabled = true; return this; } public CorsSetting disable() { enabled = false; return this; } public CorsSetting allowOrigin(String allowOrigin) { E.illegalArgumentIf(S.blank(allowOrigin), "allow origin cannot be empty"); this.allowOrigin = allowOrigin; return this; } public CorsSetting maxAge(int maxAge) { E.illegalArgumentIf(maxAge < 0); this.maxAge = maxAge; return this; } public CorsSetting allowHeaders(String ... headers) { headersAllowed.addAll(C.listOf(headers)); return this; } public CorsSetting exposeHeaders(String ... headers) { headersExpose.addAll(C.listOf(headers)); return this; } public CorsSetting allowAndExposeHeaders(String ... headers) { headersBoth.addAll(C.listOf(headers)); return this; } private void checkAndCommit() { if (!enabled) { logger.info("Global CORS is disabled"); conf.enableCors(false); return; } logger.info("Global CORS is enabled"); conf.enableCors(true); conf.corsAllowOrigin(allowOrigin); conf.corsHeaders(consolidate(headersBoth)); conf.corsAllowHeaders(consolidate(headersAllowed)); conf.corsHeadersExpose(consolidate(headersExpose)); conf.corsMaxAge(maxAge); } private String consolidate(List<String> stringList) { if (stringList.isEmpty()) { return null; } Set<String> set = new HashSet<String>(); for (String s : stringList) { set.addAll(C.listOf(s.split(","))); } return S.join(", ", set); } } }