View Javadoc
1   package net.avcompris.commons3.core.impl;
2   
3   import static com.google.common.base.Preconditions.checkNotNull;
4   import static com.google.common.base.Preconditions.checkState;
5   import static com.google.common.collect.Maps.newHashMap;
6   
7   import java.lang.annotation.Annotation;
8   import java.lang.reflect.InvocationTargetException;
9   import java.lang.reflect.Method;
10  import java.util.Map;
11  
12  import javax.annotation.Nullable;
13  
14  import org.springframework.beans.factory.annotation.Autowired;
15  import org.springframework.stereotype.Component;
16  import org.springframework.stereotype.Service;
17  
18  import net.avcompris.commons3.api.EnumPermission;
19  import net.avcompris.commons3.api.EnumRole;
20  import net.avcompris.commons3.api.User;
21  import net.avcompris.commons3.api.exception.UnauthorizedException;
22  import net.avcompris.commons3.core.Permissions;
23  
24  @Component
25  public final class PermissionsImpl implements Permissions {
26  
27  	@Autowired
28  	public PermissionsImpl() {
29  
30  	}
31  
32  	static class CallContext {
33  
34  		private final EnumPermission declaredPermissionType;
35  
36  		@SuppressWarnings("unused")
37  		private final String username;
38  
39  		@SuppressWarnings("unused")
40  		private final Object[] contextNameValuePairs;
41  
42  		CallContext(final EnumPermission declaredPermissionType, final String username,
43  				final Object... contextNameValuePairs) {
44  
45  			this.declaredPermissionType = checkNotNull(declaredPermissionType, "declaredPermissionType");
46  			this.username = checkNotNull(username, "username");
47  			this.contextNameValuePairs = checkNotNull(contextNameValuePairs, "contextNameValuePairs");
48  		}
49  
50  		public boolean permissionMatches(final EnumPermission permission) throws UnauthorizedException {
51  
52  			checkNotNull(permission, "permission");
53  
54  			if (permission.isSuperadminPermission()) {
55  
56  				return true; // SUPERADMIN can do all
57  			}
58  
59  			if (permission == declaredPermissionType) {
60  
61  				return true;
62  			}
63  
64  			return false;
65  		}
66  	}
67  
68  	static boolean matches(final EnumRole role, final CallContext callContext) throws UnauthorizedException {
69  
70  		for (final EnumRole superRole : role.getSuperRoles()) {
71  
72  			if (matches(superRole, callContext)) {
73  
74  				return true;
75  			}
76  		}
77  
78  		for (final EnumPermission permission : role.getPermissions()) {
79  
80  			if (callContext.permissionMatches(permission)) {
81  
82  				return true;
83  			}
84  		}
85  
86  		return false;
87  	}
88  
89  	@Override
90  	public void assertAuthorized(final String correlationId, final User user, final Object... contextNameValuePairs)
91  			throws UnauthorizedException {
92  
93  		checkNotNull(correlationId, "correlationId");
94  		checkNotNull(user, "user");
95  		checkNotNull(contextNameValuePairs, "contextNameValuePairs");
96  
97  		// Implementation note: This method should look for the implemented service
98  		// interface, such as "UsersService", etc., then fetch the outer-most
99  		// method, and infer the required EnumPermission, then check the compatibility
100 		// with the UserPermissions passed in the User object.
101 
102 		final EnumPermission declaredPermissionType = getCurrentServiceMethodDeclaredPermissionType();
103 
104 		if (declaredPermissionType.isAnyUserPermission()) {
105 			return;
106 		}
107 
108 		final String username = user.getUsername();
109 
110 		final CallContext callContext = new CallContext(declaredPermissionType, username, contextNameValuePairs);
111 
112 		final EnumRole role = user.getRole();
113 
114 		if (matches(role, callContext)) {
115 
116 			return;
117 		}
118 
119 		System.err.println("ERROR *** Could not find permissionType: " + declaredPermissionType + " for username: "
120 				+ user.getUsername());
121 
122 		for (final EnumRole superRole : role.getSuperRoles()) {
123 
124 			System.err.println("    Found: " + user.getUsername() + "/" + superRole);
125 		}
126 
127 		for (final EnumPermission permission : role.getPermissions()) {
128 
129 			System.err.println("    Found: " + user.getUsername() + "/" + permission);
130 		}
131 
132 		throw new UnauthorizedException(declaredPermissionType + " (" + username + ")");
133 	}
134 
135 	private static EnumPermission getCurrentServiceMethodDeclaredPermissionType() {
136 
137 		for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
138 
139 			final String className = ste.getClassName();
140 
141 			final Class<?> clazz;
142 
143 			try {
144 
145 				clazz = Class.forName(className);
146 
147 			} catch (final ClassNotFoundException e) {
148 
149 				throw new RuntimeException(e);
150 			}
151 
152 			// See: ServiceImplClassesTest in shared3-ut for a check on this annotation.
153 			//
154 			if (clazz.getAnnotation(Service.class) == null) {
155 				continue;
156 			}
157 
158 			final String methodName = ste.getMethodName();
159 
160 			for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
161 
162 				for (final Class<?> i : c.getInterfaces()) {
163 
164 					for (final Method method : i.getMethods()) {
165 
166 						if (!methodName.contentEquals(method.getName())) {
167 							continue;
168 						}
169 
170 						for (final Annotation annotation : method.getAnnotations()) {
171 
172 							final EnumPermission permissionType = loadAnnotatedPermissionType(annotation);
173 
174 							if (permissionType != null) {
175 								return permissionType;
176 							}
177 						}
178 					}
179 				}
180 			}
181 
182 			throw new IllegalStateException(
183 					"Cannot find @PermissionTypes annotation for: " + className + "." + methodName + "()");
184 		}
185 
186 		throw new IllegalStateException("Cannot find @Service + @PermissionTypes annotations for current call.");
187 	}
188 
189 	private static final class AnnotatedPermissionType {
190 
191 		private static final AnnotatedPermissionType NULL_INSTANCE = new AnnotatedPermissionType(null);
192 
193 		private static final Map<Annotation, AnnotatedPermissionType> ANNOTATED_PERMISSION_TYPES = newHashMap();
194 
195 		@Nullable
196 		public final EnumPermission permissionType;
197 
198 		private AnnotatedPermissionType(@Nullable final EnumPermission permissionType) {
199 
200 			this.permissionType = permissionType;
201 		}
202 
203 		@Nullable
204 		public static AnnotatedPermissionType getCached(final Annotation annotation) {
205 
206 			return ANNOTATED_PERMISSION_TYPES.get(annotation);
207 		}
208 
209 		@Nullable
210 		public static EnumPermission putNullPermissionTypeFor(final Annotation annotation) {
211 
212 			ANNOTATED_PERMISSION_TYPES.put(annotation, NULL_INSTANCE);
213 
214 			return NULL_INSTANCE.permissionType;
215 		}
216 
217 		@Nullable
218 		public static EnumPermission putPermissionTypeFor(final Annotation annotation,
219 				@Nullable final EnumPermission permissionType) {
220 
221 			final AnnotatedPermissionType annotated = new AnnotatedPermissionType(permissionType);
222 
223 			ANNOTATED_PERMISSION_TYPES.put(annotation, annotated);
224 
225 			return permissionType;
226 		}
227 	}
228 
229 	@Nullable
230 	private static EnumPermission loadAnnotatedPermissionType(final Annotation annotation) {
231 
232 		final AnnotatedPermissionType cached = AnnotatedPermissionType.getCached(annotation);
233 
234 		if (cached != null) {
235 			return cached.permissionType;
236 		}
237 
238 		final Method method;
239 
240 		try {
241 
242 			method = annotation.annotationType().getMethod("value");
243 
244 		} catch (final NoSuchMethodException e) {
245 
246 			return AnnotatedPermissionType.putNullPermissionTypeFor(annotation);
247 		}
248 
249 		final Class<?> returnType = method.getReturnType();
250 
251 		if (returnType == null) {
252 
253 			return AnnotatedPermissionType.putNullPermissionTypeFor(annotation);
254 
255 		} else if (EnumPermission.class.isAssignableFrom(returnType)) {
256 
257 			final EnumPermission permissionType = extractPermissionTypeValueFrom(annotation, method);
258 
259 			return AnnotatedPermissionType.putPermissionTypeFor(annotation, permissionType);
260 
261 		} else if (EnumPermission[].class.isAssignableFrom(returnType)) {
262 
263 			final EnumPermission[] permissionTypes = extractPermissionTypeValuesFrom(annotation, method);
264 
265 			if (permissionTypes == null || permissionTypes.length == 0) {
266 				return AnnotatedPermissionType.putNullPermissionTypeFor(annotation);
267 			}
268 
269 			checkState(permissionTypes.length == 1, //
270 					"PermissionTypes.length should be 1, but was: %s", permissionTypes.length);
271 
272 			return AnnotatedPermissionType.putPermissionTypeFor(annotation, permissionTypes[0]);
273 
274 		} else {
275 
276 			return AnnotatedPermissionType.putNullPermissionTypeFor(annotation);
277 		}
278 	}
279 
280 	@Nullable
281 	private static EnumPermission extractPermissionTypeValueFrom(final Annotation annotation, final Method method) {
282 
283 		final Object result;
284 
285 		try {
286 
287 			result = method.invoke(annotation);
288 
289 		} catch (final InvocationTargetException | IllegalAccessException e) {
290 
291 			return null;
292 		}
293 
294 		return (EnumPermission) result;
295 	}
296 
297 	@Nullable
298 	private static EnumPermission[] extractPermissionTypeValuesFrom(final Annotation annotation, final Method method) {
299 
300 		final Object result;
301 
302 		try {
303 
304 			result = method.invoke(annotation);
305 
306 		} catch (final InvocationTargetException | IllegalAccessException e) {
307 
308 			return null;
309 		}
310 
311 		return (EnumPermission[]) result;
312 	}
313 }