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;
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
98
99
100
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
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 }