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