Implementing simple ldap user details mapper - grails - spring

In my previous question i was working on adding roles to users logging from ldap. hopefully ive managed to register custom AuthoritiesPopulator for ldap. But now i would like to add some more functionallity to application and for that i need some more information about users than login-name.
Following this guide:
http://grails-plugins.github.io/grails-spring-security-ldap/docs/manual.106/guide/2.%20Usage.html
Im guessing i have to implement my own ldap details context mapper, and i did as shown below:
class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
// FETCHING DATA and ADDING ROLE
return new CustomUserDetails(trueUsername, null, true, true, true, true, list, email)
}
#Override
public void mapUserToContext(UserDetails arg0, DirContextAdapter arg1) {
throw new IllegalStateException("Only retrieving data from AD is currently supported")
}
}
And adding mapping in resource.config:
ldapUserDetailsMapper(amelinium1.grails.CustomUserDetailsContextMapper)
But it doesn't seem to work. Application seem to use GormUserDetailsService to return User not the Context. Am i missing something here ?
Would appreciate any help!

Related

JPA AuditorAware not getting created for each user

I have implemented auditing using JPA auditing. My code looks like this:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfiguration {
#Bean
#Scope(value= ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public AuditorAware<String> auditorAware() {
final String currentUser;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(null != authentication) {
currentUser = authentication.getName();
} else {
currentUser = null;
}
return () -> Optional.ofNullable(currentUser);
}
}
The issue I am facing is if I login with one user and perform some operation, it's working fine. But when I logout and login with another user, It's still using the last user only.
After debugging the code what I found is spring not creating bean of AuditorAware for each user. It's behaving like singleton bean. Even I specify the scope as prototype also, still it's behaving like singleton.
The AuditorAware is supposed to be a singleton. You should retrieve the current user, each time the AuditAware.getCurrentAuditor is called. Not just once.
Rewrite your code to something like this.
#Bean
public AuditorAware<String> auditorAware() {
return () -> getCurrentAuthentication().map(Authentication::getName());
}
private Optional<Authentication> getCurrentAuthentication() {
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication());
}

How to remove existing sessions by specific principal name

I'm using Spring Session 1.3.0 with Redis backend in my project.
I have an use case that the super admin might update the roles of existing user who might already logged in. I want to delete the existing session records for those users after changing their roles.
Is there API of Spring Session to archive it?
#Autowired
private SessionRegistry sessionRegistry;
public void expireUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof User) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation information : sessionRegistry.getAllSessions(userDetails, true)) {
information.expireNow();
}
}
}
}
}
Also work out another way to clean sessions of specific user,
#Autowired
FindByIndexNameSessionRepository sessionRepository;
sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
username).keySet().forEach(session -> sessionRepository.delete((String) session));

spring security LDAP get additional fields

I am using Spring Security with LDAP (Active directory), I am able to authenticate user and create my own user detail object by extending LdapUserDetailsMapper.
By default I am getting certain fields and groups and DN.
But I would like to get additional fields, like email, contact number, which are available in Active Directory.
So how to get those information ?
My configuration
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("hmie.co.in", "ldap://1.1.1.1:389/");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper);
return provider;
}
Custom user detail mapping
#Service
public class MyUserDetailsContextMapper extends LdapUserDetailsMapper implements UserDetailsContextMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
LdapUserDetailsImpl ldapUserDetailsImpl = (LdapUserDetailsImpl) super.mapUserFromContext(ctx, username, authorities);
MyUserDetails myUserDetails = new MyUserDetails();
myUserDetails.setAccountNonExpired(ldapUserDetailsImpl.isAccountNonExpired());
myUserDetails.setAccountNonLocked(ldapUserDetailsImpl.isAccountNonLocked());
myUserDetails.setCredentialsNonExpired(ldapUserDetailsImpl.isCredentialsNonExpired());
myUserDetails.setEnabled(ldapUserDetailsImpl.isEnabled());
myUserDetails.setUsername(ldapUserDetailsImpl.getUsername());
myUserDetails.setAuthorities(ldapUserDetailsImpl.getAuthorities());
String dn = ldapUserDetailsImpl.getDn();
int beginIndex = dn.indexOf("cn=") + 3;
int endIndex = dn.indexOf(",");
myUserDetails.setEmployeeName(dn.substring(beginIndex, endIndex));
beginIndex = dn.indexOf("ou=") + 3;
endIndex = dn.indexOf(",", beginIndex);
myUserDetails.setDepartment(dn.substring(beginIndex, endIndex));
return myUserDetails;
}
}
To get the complete LDAP Directory attributes and values i did like this. But here i am using inteface org.springframework.ldap.core.AttributesMapper instead of class org.springframework.security.ldap.userdetails.LdapUserDetailsMapper.
ldapTemplate.search("o=XXXXX", new EqualsFilter("uid", userName).encode(),
new AttributesMapper() {
#Override
public Object mapFromAttributes(Attributes attr) throws NamingException {
// TODO Auto-generated method stub
NamingEnumeration<String> namingEnumeration = attr.getIDs();
while (namingEnumeration.hasMoreElements()) {
String attributeName= (String) namingEnumeration.nextElement();
System.out.println(attributeName+" = "+attr.get(attributeName));
}
return null;
}
});
In the above piece of code attr.getIDs() returns the Active directory attributes like CN,DN,SN and mail. attr.get(attribute) returns the value of attribute.
The code in mapUserFromContext is so close! The key detail is that the ctx object passed in to the method already contains the additional Active Directory attributes for the principal. The attribute values are accessible using method ctx.getStringAttribute("attribute-name"). For example, you would access the surname attribute of the principal with ctx.getStringAttribute("sn"). To get the user's email and contact number, you would only need to access the appropriate attributes. In my company's Active Directory, those attributes are mail and phone, respectively. The attributes might be named differently in your system.

capture the third party web service session Id during Spring Security Session

I have implemented Spring security in a Spring MVC web application.
For the authentication purpose I am using LDAP and for authorization I am calling a third party Web Service that provides me All the authorizations and also a Session Id.
Once user log out or session timeout, I need to call the third party web service again with the same session Id for invalidation of session.
I have created a Log out Listener that listen to SessionDestroyedEvent like this
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent>{
private SecurityServiceHandler securityServiceHandler;
#Override
public void onApplicationEvent(SessionDestroyedEvent event) {
SecurityContext securityContext = event.getSecurityContext();
UserDetails ud=null;
if(securityContext!=null){
ud = (UserDetails) securityContext.getAuthentication().getPrincipal();
if(securityServiceHandler==null){
securityServiceHandler = new SecurityServiceHandler();
}
//String sessionId = securityServiceHandler.getSessionId();
String sessionId = VirgoSessionManager.getSessionId();
System.out.println(ud.getUsername());
System.out.println(VirgoSessionManager.getSessionId());
securityServiceHandler.invalidateSession(ud.getUsername(),sessionId);
//reset the sessionId
securityServiceHandler.setSessionId(null);
}
}
I have used ThreadLocal in the VirgoSessionManager Class like follow
public class VirgoSessionManager {
private static ThreadLocal<String> sessionId = new ThreadLocal<String>();
public static String getSessionId(){
return sessionId.get();
}
public static void setSessionId(String sId) {
sessionId.set(sId);
}
public static void remove() {
sessionId.remove();
}
}
My problem is the that The VirgoSessionManager is not returning the session I have set during the Third party Session creation call after successful session cration even though I have implemented thread Local.
Any help will be appreciated.
Thank you!
you can have completely different thread serving your log out functionality which results in not having any value in ThreadLocal variable. For example tomcat uses thread pools so you need to be careful here. Try to log it/debug using Thread.currentThread().getName() and Thread.currentThread().getId() in getSessionId() and a place you set this value
I fixed the issue with the separate thread calling the Logout/Session time out.
I created a new customized User class and extended the Original spring "org.springframework.security.core.userdetails.User" class. I added new field "sessionId" to my customized user class.
So whenever I get the logged user details from Spring SecurityContext during logout/timeout, I will always have that sessionId and use to call invalidateSession method.
My customized user class looks like this.
package com.wvo.custom.security;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
public class CustomUser extends User {
private String virgoSessionId ;
/**
*
*/
private static final long serialVersionUID = 1L;
public CustomUser(String username, String password,boolean enabled, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired,
Collection<? extends GrantedAuthority> authorities) {
super(username, password,enabled, accountNonExpired, accountNonLocked, credentialsNonExpired, authorities);
}
public String getVirgoSessionId() {
return virgoSessionId;
}
public void setVirgoSessionId(String virgoSessionId) {
this.virgoSessionId = virgoSessionId;
}
}
Thank you !

Vaadin close UI of same user in another browser/tab/system

I'm doing a project in Vaadin 7. In that I need to implement something like below for the login.
A user 'A' is logged in to a system '1'. And again he logs into another system '2'. Now I want to know how to close the UI on the system '1'.
I tried something and can able to close the UI, If it is the same browser. But, for different systems/browser. I don't have any idea.
My Code:
private void closeUI(String attribute) {
for (UI ui : getSession().getUIs()) {
if(ui.getSession().getAttribute(attribute) != null)
if(ui.getSession().getAttribute(attribute).equals(attribute))
ui.close();
}
}
Can anyone help me in this?
I have a situation similar to your where I need to display several info regarding all sessions. What I did was I created my own Servlet extending the VaadinServlet with a static ConcurrentHashmap to save my sessions info, and a SessionDestroyListener to remove any info from the map upon logout. Initially I also had a SessionInitListener where I added the info in the hashmap but I realized I only had the user information after authentication so I moved this part to the page handling the login.
I guess you could do something similar, or at least this should get you started:
public class SessionInfoServlet extends VaadinServlet {
private static final ConcurrentHashMap<User, VaadinSession> userSessionInfo = new ConcurrentHashMap<>();
// this could be called after login to save the session info
public static void saveUserSessionInfo(User user, VaadinSession session) {
VaadinSession oldSession = userSessionInfo.get(user);
if(oldSession != null){
// close the old session
oldSession.close();
}
userSessionInfo.put(user, session);
}
public static Map<User, VaadinSession> getUserSessionInfos() {
// access the cache if we need to, otherwise useless and removable
return userSessionInfo;
}
#Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
// register our session destroy listener
SessionLifecycleListener sessionLifecycleListener = new SessionLifecycleListener();
getService().addSessionDestroyListener(sessionLifecycleListener);
}
private class SessionLifecycleListener implements SessionDestroyListener {
#Override
public void sessionDestroy(SessionDestroyEvent event) {
// remove saved session from cache, for the user that was stored in it
userSessionInfo.remove(event.getSession().getAttribute("user"));
}
}
}

Resources