001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004
005 This file is part of Granite Data Services.
006
007 Granite Data Services is free software; you can redistribute it and/or modify
008 it under the terms of the GNU Library General Public License as published by
009 the Free Software Foundation; either version 2 of the License, or (at your
010 option) any later version.
011
012 Granite Data Services is distributed in the hope that it will be useful, but
013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 for more details.
016
017 You should have received a copy of the GNU Library General Public License
018 along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020
021 package org.granite.client.tide.javafx.spring;
022
023 import java.util.HashMap;
024 import java.util.Map;
025 import java.util.Observable;
026 import java.util.Observer;
027 import java.util.concurrent.Future;
028
029 import javafx.beans.property.BooleanProperty;
030 import javafx.beans.property.ReadOnlyBooleanPropertyBase;
031 import javafx.beans.property.ReadOnlyStringWrapper;
032 import javafx.beans.property.SimpleBooleanProperty;
033 import javafx.beans.property.StringProperty;
034 import javafx.beans.value.ChangeListener;
035 import javafx.beans.value.ObservableValue;
036
037 import org.granite.client.messaging.messages.responses.FaultMessage;
038 import org.granite.client.messaging.messages.responses.FaultMessage.Code;
039 import org.granite.client.tide.Context;
040 import org.granite.client.tide.impl.ComponentImpl;
041 import org.granite.client.tide.server.ExceptionHandler;
042 import org.granite.client.tide.server.ServerSession;
043 import org.granite.client.tide.server.SimpleTideResponder;
044 import org.granite.client.tide.server.TideFaultEvent;
045 import org.granite.client.tide.server.TideResponder;
046 import org.granite.client.tide.server.TideResultEvent;
047 import org.granite.client.util.WeakIdentityHashMap;
048 import org.granite.messaging.amf.RemoteClass;
049
050 /**
051 * @author William DRAI
052 */
053 @RemoteClass("org.granite.tide.spring.security.Identity")
054 public class Identity extends ComponentImpl implements ExceptionHandler {
055
056 private BooleanProperty loggedIn = new SimpleBooleanProperty(this, "loggedIn");
057 private StringProperty username = new ReadOnlyStringWrapper(this, "username", null);
058
059
060 public BooleanProperty loggedInProperty() {
061 return loggedIn;
062 }
063
064 public boolean isLoggedIn() {
065 return loggedIn.get();
066 }
067
068 public void setLoggedIn(boolean loggedIn) {
069 this.loggedIn.set(loggedIn);
070 }
071
072 public StringProperty usernameProperty() {
073 return username;
074 }
075
076 public String getUsername() {
077 return username.get();
078 }
079
080
081 public Identity(final ServerSession serverSession) {
082 super(serverSession);
083
084 this.loggedIn.set(false);
085 this.loggedIn.addListener(new ChangeListener<Boolean>() {
086 @Override
087 public void changed(ObservableValue<? extends Boolean> property, Boolean oldValue, Boolean newValue) {
088 if (Boolean.TRUE.equals(newValue)) {
089 initSecurityCache();
090 serverSession.afterLogin();
091 }
092 }
093 });
094 }
095
096 /**
097 * Triggers a remote call to check is user is currently logged in
098 * Can be used at application startup to handle browser refresh cases
099 *
100 * @param resultHandler optional result handler
101 * @param faultHandler optional fault handler
102 */
103 public Future<String> checkLoggedIn(final TideResponder<String> tideResponder) {
104 return super.call("isLoggedIn", new SimpleTideResponder<String>() {
105 @Override
106 public void result(TideResultEvent<String> event) {
107 if (event.getResult() != null) {
108 Identity.this.username.set(event.getResult());
109 Identity.this.loggedIn.set(true);
110 }
111
112 tideResponder.result(event);
113 }
114
115 @Override
116 public void fault(TideFaultEvent event) {
117 Identity.this.username.set(null);
118 Identity.this.loggedIn.set(false);
119
120 tideResponder.fault(event);
121 }
122 });
123 }
124
125
126 public void login(final String username, String password, final TideResponder<String> tideResponder) {
127 getServerSession().login(username, password);
128
129 checkLoggedIn(tideResponder);
130 }
131
132
133 public void logout(final TideResponder<Void> tideResponder) {
134 final Observer observer = new Observer() {
135 @SuppressWarnings("unchecked")
136 @Override
137 public void update(Observable logout, Object event) {
138 Identity.this.loggedIn.set(false);
139 Identity.this.username.set(null);
140
141 clearSecurityCache();
142
143 if (tideResponder != null) {
144 if (event instanceof TideResultEvent)
145 tideResponder.result((TideResultEvent<Void>)event);
146 else if (event instanceof TideFaultEvent)
147 tideResponder.fault((TideFaultEvent)event);
148 }
149 }
150 };
151
152 getServerSession().logout(observer);
153 }
154
155
156 private Map<String, ObservableRole> ifAllGrantedCache = new HashMap<String, ObservableRole>();
157 private Map<String, ObservableRole> ifAnyGrantedCache = new HashMap<String, ObservableRole>();
158 private Map<String, ObservableRole> ifNotGrantedCache = new HashMap<String, ObservableRole>();
159
160
161 public ObservableRole ifAllGranted(String roleName) {
162 ObservableRole role = ifAllGrantedCache.get(roleName);
163 if (role == null) {
164 role = new ObservableRole(this, "ifAllGranted", roleName);
165 ifAllGrantedCache.put(roleName, role);
166 }
167 return role;
168 }
169
170 public ObservableRole ifAnyGranted(String roleName) {
171 ObservableRole role = ifAnyGrantedCache.get(roleName);
172 if (role == null) {
173 role = new ObservableRole(this, "ifAnyGranted", roleName);
174 ifAnyGrantedCache.put(roleName, role);
175 }
176 return role;
177 }
178
179 public ObservableRole ifNotGranted(String roleName) {
180 ObservableRole role = ifNotGrantedCache.get(roleName);
181 if (role == null) {
182 role = new ObservableRole(this, "ifNotGranted", roleName);
183 ifNotGrantedCache.put(roleName, role);
184 }
185 return role;
186 }
187
188 public class ObservableRole extends ReadOnlyBooleanPropertyBase {
189
190 private final Object bean;
191 private final String name;
192 private final String roleName;
193
194 private Boolean hasRole = null;
195
196
197 public ObservableRole(Object bean, String name, String roleName) {
198 super();
199 this.bean = bean;
200 this.name = name;
201 this.roleName = roleName;
202 }
203
204 @Override
205 public Object getBean() {
206 return bean;
207 }
208
209 @Override
210 public String getName() {
211 return name + "." + roleName;
212 }
213
214 @Override
215 public boolean get() {
216 if (hasRole == null) {
217 if (isLoggedIn())
218 getFromRemote(null);
219 return false;
220 }
221 return hasRole;
222 }
223
224 public boolean get(TideResponder<Boolean> tideResponder) {
225 if (hasRole != null) {
226 if (tideResponder != null) {
227 TideResultEvent<Boolean> event = new TideResultEvent<Boolean>(getContext(), getServerSession(), null, hasRole);
228 tideResponder.result(event);
229 }
230 return hasRole;
231 }
232 if (isLoggedIn())
233 getFromRemote(tideResponder);
234 return false;
235 }
236
237 public void getFromRemote(final TideResponder<Boolean> tideResponder) {
238 Identity.this.call(name, roleName, new SimpleTideResponder<Boolean>() {
239 @Override
240 public void result(TideResultEvent<Boolean> event) {
241 hasRole = event.getResult();
242 fireValueChangedEvent();
243 }
244
245 @Override
246 public void fault(TideFaultEvent event) {
247 clear();
248 }
249 });
250 }
251
252 void clear() {
253 hasRole = null;
254 fireValueChangedEvent();
255 }
256 }
257
258
259 private Map<Object, Map<String, ObservablePermission>> permissionsCache = new WeakIdentityHashMap<Object, Map<String, ObservablePermission>>();
260
261 public ObservablePermission hasPermission(Object entity, String action) {
262 Map<String, ObservablePermission> entityPermissions = permissionsCache.get(entity);
263 if (entityPermissions == null) {
264 entityPermissions = new HashMap<String, ObservablePermission>();
265 permissionsCache.put(entity, entityPermissions);
266 }
267 ObservablePermission permission = entityPermissions.get(action);
268 if (permission == null) {
269 permission = new ObservablePermission(this, "hasPermission", entity, action);
270 entityPermissions.put(action, permission);
271 }
272 return permission;
273 }
274
275 public class ObservablePermission extends ReadOnlyBooleanPropertyBase {
276
277 private final Object bean;
278 private final String name;
279 private final Object entity;
280 private final String action;
281
282 private Boolean hasPermission = null;
283
284
285 public ObservablePermission(Object bean, String name, Object entity, String action) {
286 super();
287 this.bean = bean;
288 this.name = name;
289 this.entity = entity;
290 this.action = action;
291 }
292
293 @Override
294 public Object getBean() {
295 return bean;
296 }
297
298 @Override
299 public String getName() {
300 return name;
301 }
302
303 @Override
304 public boolean get() {
305 if (hasPermission == null) {
306 if (isLoggedIn())
307 getFromRemote(null);
308 return false;
309 }
310 return hasPermission;
311 }
312
313 public boolean get(TideResponder<Boolean> tideResponder) {
314 if (hasPermission != null) {
315 if (tideResponder != null) {
316 TideResultEvent<Boolean> event = new TideResultEvent<Boolean>(getContext(), getServerSession(), null, hasPermission);
317 tideResponder.result(event);
318 }
319 return hasPermission;
320 }
321 if (isLoggedIn())
322 getFromRemote(tideResponder);
323 return false;
324 }
325
326 public void getFromRemote(final TideResponder<Boolean> tideResponder) {
327 Identity.this.call(name, entity, action, new SimpleTideResponder<Boolean>() {
328 @Override
329 public void result(TideResultEvent<Boolean> event) {
330 hasPermission = event.getResult();
331 fireValueChangedEvent();
332 }
333
334 @Override
335 public void fault(TideFaultEvent event) {
336 clear();
337 }
338 });
339 }
340
341 void clear() {
342 hasPermission = null;
343 fireValueChangedEvent();
344 }
345 }
346
347
348 private void initSecurityCache() {
349 for (ObservableRole role : ifAllGrantedCache.values())
350 role.clear();
351 for (ObservableRole role : ifAnyGrantedCache.values())
352 role.clear();
353 for (ObservableRole role : ifNotGrantedCache.values())
354 role.clear();
355
356 for (Map<String, ObservablePermission> entityPermissions : permissionsCache.values()) {
357 for (ObservablePermission permission : entityPermissions.values())
358 permission.clear();
359 }
360 }
361
362 /**
363 * Clear the security cache
364 */
365 public void clearSecurityCache() {
366 for (ObservableRole role : ifAllGrantedCache.values())
367 role.clear();
368 ifAllGrantedCache.clear();
369 for (ObservableRole role : ifAnyGrantedCache.values())
370 role.clear();
371 ifAnyGrantedCache.clear();
372 for (ObservableRole role : ifNotGrantedCache.values())
373 role.clear();
374 ifNotGrantedCache.clear();
375
376 for (Map<String, ObservablePermission> entityPermissions : permissionsCache.values()) {
377 for (ObservablePermission permission : entityPermissions.values())
378 permission.clear();
379 }
380 permissionsCache.clear();
381 }
382
383 @Override
384 public boolean accepts(FaultMessage emsg) {
385 return emsg.getCode() == Code.NOT_LOGGED_IN;
386 }
387
388 @Override
389 public void handle(Context context, FaultMessage emsg, TideFaultEvent faultEvent) {
390 if (isLoggedIn()) {
391 // Session expired, directly mark the channel as logged out
392 getServerSession().sessionExpired();
393 setLoggedIn(false);
394 }
395 }
396
397 }
398