All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.agapsys.security.Security Maven / Gradle / Ivy

/*
 * Copyright 2016 Agapsys Tecnologia Ltda-ME.
 *
 * 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.
 */
package com.agapsys.security;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedHashSet;
import java.util.Set;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

/**
 * Class responsible by security preventing unexpected method executions
 * @author Leandro Oliveira ([email protected])
 */
public class Security {

	// CLASS SCOPE =============================================================	
	private static final String EMBEDDED_PROTECTED_CLASS_LIST_FILE = "META-INF/security.info";
	private static final String EMBEDDED_PROTECTED_CLASS_LIST_FILE_ENCODING = "utf-8";

	// Core functionality ------------------------------------------------------
	private static void init(ClassLoader classLoader, SecurityManager securityManager, Set securedClasses) {
		if (classLoader == null)
			throw new IllegalArgumentException("A class loader must be provided");
		
		if (securityManager == null)
			throw new IllegalArgumentException("A security manager must be provided");
		
		if (securedClasses == null)
			throw new IllegalArgumentException("Secured classes cannot be null");
		
		Security.securityManager = securityManager;

		ClassPool cp = ClassPool.getDefault();

		for (String securedClass : securedClasses) {
			secure(classLoader, cp, securedClass);
		}
	}

	private static Set readSecurityInfo(InputStream is, String encoding) {
		try {
			BufferedReader in = new BufferedReader(new InputStreamReader(is, encoding));
			Set classes = new LinkedHashSet<>();
			String readLine;

			while ((readLine = in.readLine()) != null) {
				readLine = readLine.trim();
				
				if (readLine.isEmpty())
					continue;
				
				if (!classes.add(readLine)) {
					throw new RuntimeException("Duplicate definition of " + readLine);
				}
			}

			return classes;
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	}

	private static Set readSecurityInfo(String embeddedFileName, String encoding) {
		try (InputStream is = Security.class.getClassLoader().getResourceAsStream(embeddedFileName)) {
			if (is != null)
				return readSecurityInfo(is, encoding);
			
			return new LinkedHashSet<>();
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	}

	private static String toScCommaDelimited(IterablestrIterable, boolean encloseInDoubleQuotes) {
		StringBuilder sb = new StringBuilder();

		boolean first = true;
		for (String str : strIterable) {
			if (!first) {
				sb.append(", ");
			}
			
			if (encloseInDoubleQuotes)
				sb.append("\"");
			
			sb.append(str);
			
			if (encloseInDoubleQuotes)
				sb.append("\"");
			
			first = false;
		}

		return sb.toString();
	}

	private static boolean logEnabled = false;
	private static boolean skipFrozenClasses = false;
	
	/** 
	 * Enables/Disables console logging output.
	 * @param enable defines if log messages shall be printed to console. By default log is disabled.
	 */
	public static void enableLog(boolean enable) {
		logEnabled = enable;
	}
	
	/**
	 * Defines if frozen classes shall be skipped.
	 * @param skip define if frozen classes shall be skipped instead of rising errors. By default frozen classes are not skipped.
	 */
	public static void skipFrozenClasses(boolean skip) {
		skipFrozenClasses = skip;
	}
	
	private static void log(String message, Object...msgArgs) {
		if (logEnabled) {
			if (msgArgs.length > 0) message = String.format(message, msgArgs);
			System.out.println(message);
		}
	}
	
	private static void secure(ClassLoader classLoader, ClassPool cp, String className) {
		try {
			
			CtClass cc = cp.get(className);
			
			if (!skipFrozenClasses || !cc.isFrozen()) {
				CtMethod methods[] = cc.getDeclaredMethods();
				Secured securedClassAnnotation = (Secured) cc.getAnnotation(Secured.class);

				for (CtMethod method : methods) {
					Secured securedMethodAnnotation = (Secured) method.getAnnotation(Secured.class);
					Unsecured unsecuredMethodAnnotation = (Unsecured) method.getAnnotation(Unsecured.class);
					
					if (securedMethodAnnotation != null && unsecuredMethodAnnotation != null)
						throw new RuntimeException(String.format("Method '%s' has both '%s' and '%s' annotations", method.getLongName(), Secured.class.getName(), Unsecured.class.getName()));

					if (unsecuredMethodAnnotation == null && (securedClassAnnotation != null || securedMethodAnnotation != null)) {
						Set roles = new LinkedHashSet<>();

						if (securedClassAnnotation != null) {
							for (String role : securedClassAnnotation.value()) {
								if (!roles.add(role))
									throw new RuntimeException(String.format("Duplicate role definition (%s) for %s", role, cc.getName()));
							}
						}

						if (securedMethodAnnotation != null) {
							for (String role : securedMethodAnnotation.value()) {
								if (!roles.add(role))
									throw new RuntimeException(String.format("Duplicate role definition (%s) for %s", role, method.getLongName()));
							}
						}

						String scVarRoles = roles.isEmpty() ? "String[] roles = new String[0]" : String.format("String[] roles = {%s}", toScCommaDelimited(roles, true));
						String scVarSecurityManager = "com.agapsys.security.SecurityManager sm = com.agapsys.security.Security.getSecurityManager()";
						String sc = String.format("{ %s; %s; if (!sm.isAllowed(roles)) { sm.onNotAllowed(); } }", scVarRoles, scVarSecurityManager);
						method.insertBefore(sc);
					}
				}
			
				cc.toClass(classLoader, Security.class.getProtectionDomain());
				log("Secured class: %s", className);
			} else {
				log("Class already secured: %s", className);
			}
		} catch (Throwable t) {
			if (t instanceof RuntimeException) {
				throw (RuntimeException) t;
			}

			throw new RuntimeException(t);
		}
	}
	// -------------------------------------------------------------------------

	private static SecurityManager securityManager = null;

	/**
	 * Returns the {@linkplain SecurityManager} instance used by framework.
	 *
	 * @return the {@linkplain SecurityManager} instance used by framework.
	 */
	public static SecurityManager getSecurityManager() {
		return securityManager;
	}

	/**
	 * Initializes security framework
	 *
	 * @param securityManager security framework to be used. Passing null implies in no security.
	 */
	protected static void init(SecurityManager securityManager) {
		init(Security.class.getClassLoader(), securityManager);
	}
	
	protected static void init(ClassLoader classLoader, SecurityManager securityManager) {
		init(classLoader, securityManager, readSecurityInfo(EMBEDDED_PROTECTED_CLASS_LIST_FILE, EMBEDDED_PROTECTED_CLASS_LIST_FILE_ENCODING));
	}

	protected static void init(SecurityManager securityManager, String... securedClasses) {
		init(Security.class.getClassLoader(), securityManager, securedClasses);
	}
	
	protected static void init(ClassLoader classLoader, SecurityManager securityManager, String... securedClasses) {
		Set protectedClassNameSet = new LinkedHashSet<>();
		
		for (int i = 0; i < securedClasses.length; i++) {
			String protectedClassName = securedClasses[i];
			
			if (protectedClassName == null || protectedClassName.trim().isEmpty())
				throw new IllegalArgumentException("Null/Empty class name at index " + i);
			
			protectedClassName = protectedClassName.trim();
			if (!protectedClassNameSet.add(protectedClassName))
				throw new IllegalArgumentException("Duplicate definition of " + protectedClassName);
		}

		init(classLoader, securityManager, protectedClassNameSet);
	}
	// =========================================================================
	
	// INSTANCE SCOPE ==========================================================
	protected Security() {}
	// =========================================================================
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy