Skip to content

#632 User authentication #773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion core/roboconf-dm-rest-commons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@
<artifactId>roboconf-dm</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ public interface UrlConstants {
String TARGETS = "targets";
String PREFERENCES = "preferences";
String SCHEDULER = "scheduler";
String AUTHENTICATION = "auth";

String SESSION_ID = "sid";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/**
* Copyright 2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.roboconf.dm.rest.commons.security;

import java.io.IOException;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

/**
* A class in charge of managing authentication and sessions.
* <p>
* Authentication is delegated to various implementations.
* By default, it is handled by Karaf's JAAS implementation, but it is possible
* to override it by using {@link #setAuthService(IAuthService)}. You will HAVE
* TO use this method if you run the REST services outside Karaf.
* </p>
* <p>
* When the authentication succeeds, a token is generated by this class
* (a random UUID in fact). The token is stored by this class and associated
* with the login time.
* </p>
* <p>
* Since sessions can be limited in time (depending on admin preferences),
* we can verify on every action that the session is still valid.
* </p>
* <p>
* To prevent "man in the middle" "attacks, authentication should be
* used along with HTTPS.
* </p>
*
* @author Vincent Zurczak - Linagora
*/
public class AuthenticationManager {

private final ConcurrentHashMap<String,Long> tokenToLoginTime = new ConcurrentHashMap<> ();
private final Logger logger = Logger.getLogger( getClass().getName());
private final String realm;

private IAuthService authService;


/**
* @author Vincent Zurczak - Linagora
*/
public static enum REST_ROLES {
ADM, TPL_RW, APP_RW, APP_R
}



/**
* Constructor.
* @param realm
*/
public AuthenticationManager( String realm ) {
this.realm = realm;
this.authService = new KarafAuthService();
this.authService.setRealm( this.realm );
}


/**
* @param authenticater the authService to set
*/
public void setAuthService( IAuthService authService ) {
this.authService = authService;
authService.setRealm( this.realm );
}


/**
* Authenticates a user and creates a new session.
* @param user a user name
* @param pwd a pass word
* @return a token if authentication worked, null if it failed
*/
public String login( String user, String pwd ) {

String token = null;
try {
this.authService.authenticate( user, pwd );

token = UUID.randomUUID().toString();
Long now = new Date().getTime();
this.tokenToLoginTime.put( token, now );

} catch( LoginException e ) {
this.logger.severe( "Invalid login attempt by user " + user );
}

return token;
}


/**
* Determines whether a session is valid.
* @param token a token
* @param validityPeriod the validity period for a session (in seconds, < 0 for unbound)
* @return true if the session is valid, false otherwise
*/
public boolean isSessionValid( final String token, long validityPeriod ) {

boolean valid = false;
Long loginTime = null;
if( token != null )
loginTime = this.tokenToLoginTime.get( token );

if( validityPeriod < 0 ) {
valid = loginTime != null;

} else if( loginTime != null ) {
long now = new Date().getTime();
valid = (now - loginTime) <= validityPeriod * 1000;

// Invalid sessions should be deleted
if( ! valid )
logout( token );
}

return valid;
}


/**
* Invalidates a session.
* <p>
* No error is thrown if the session was already invalid.
* </p>
*
* @param token a token (can be null)
*/
public void logout( String token ) {
if( token != null )
this.tokenToLoginTime.remove( token );
}


/**
* An abstraction to manage authentication.
* @author Vincent Zurczak - Linagora
*/
public interface IAuthService {

/**
* Authenticates someone by user and password.
* @param user a user name
* @param pwd a password
* @throws LoginException if authentication failed
*/
void authenticate( String user, String pwd ) throws LoginException;

/**
* Sets the REALM to use.
* @param realm a realm name
*/
void setRealm( String realm );
}


/**
* Authentication managed by Apache Karaf.
* <p>
* Karaf uses JAAS and by default supports several login modules
* (properties files, databases, LDAP, etc).
* </p>
* @author Vincent Zurczak - Linagora
*/
public static class KarafAuthService implements IAuthService {
private String realm;


@Override
public void authenticate( String user, String pwd ) throws LoginException {
LoginContext loginCtx = new LoginContext( this.realm, new RoboconfCallbackHandler( user, pwd ));
loginCtx.login();
}

@Override
public void setRealm( String realm ) {
this.realm = realm;
}
}


/**
* A callback handler for JAAS.
* @author Vincent Zurczak - Linagora
*/
static final class RoboconfCallbackHandler implements CallbackHandler {
private final String username, password;


/**
* Constructor.
* @param username
* @param password
*/
public RoboconfCallbackHandler( String username, String password ) {
this.username = username;
this.password = password;
}


@Override
public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {

for( Callback callback : callbacks ) {
if (callback instanceof NameCallback )
((NameCallback) callback).setName( this.username );
else if( callback instanceof PasswordCallback )
((PasswordCallback) callback).setPassword( this.password.toCharArray());
else
throw new UnsupportedCallbackException( callback );
}
}
}
}
Loading