Skip to content

Commit

Permalink
Jetty 9.4.x 3456 programmatic authentication (#3472)
Browse files Browse the repository at this point in the history
* Issue #3456 Allow multiple programmatic login/logout in same request.

Signed-off-by: Jan Bartel <janb@webtide.com>
janbartel authored Mar 20, 2019
1 parent 441280c commit 432fc41
Showing 12 changed files with 457 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -21,6 +21,11 @@
import java.io.Serializable;
import java.util.Set;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.UserIdentity.Scope;
@@ -95,4 +100,23 @@ public boolean declaredRolesContains(String roleName)

return false;
}

@Override
public Authentication logout (ServletRequest request)
{
SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
if (security!=null)
{
security.logout(this);
Authenticator authenticator = security.getAuthenticator();
if (authenticator instanceof LoginAuthenticator)
{
((LoginAuthenticator)authenticator).logout(request);
return new LoggedOutAuthentication((LoginAuthenticator)authenticator);
}
}

return Authentication.UNAUTHENTICATED;
}

}
Original file line number Diff line number Diff line change
@@ -80,7 +80,7 @@ public interface Authenticator
* @param mandatory True if authentication is mandatory.
* @return An Authentication. If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has
* been sent by the Authenticator (which can be done for both successful and unsuccessful authentications), then the result will
* implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}. If Authentication is not manditory, then a
* implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}. If Authentication is not mandatory, then a
* {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned.
*
* @throws ServerAuthException if unable to validate request
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//

package org.eclipse.jetty.security;

import javax.servlet.ServletRequest;

import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.UserIdentity;

/**
* LoggedOutAuthentication
*
* An Authentication indicating that a user has been previously, but is not currently logged in,
* but may be capable of logging in after a call to Request.login(String,String)
*/
public class LoggedOutAuthentication implements Authentication.NonAuthenticated
{
private LoginAuthenticator _authenticator;

public LoggedOutAuthentication (LoginAuthenticator authenticator)
{
_authenticator = authenticator;
}


@Override
public Authentication login(String username, Object password, ServletRequest request)
{
if (username == null)
return null;

UserIdentity identity = _authenticator.login(username, password, request);
if (identity != null)
{
IdentityService identity_service = _authenticator.getLoginService().getIdentityService();
UserAuthentication authentication = new UserAuthentication("API",identity);
if (identity_service != null)
identity_service.associate(identity);
return authentication;
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -581,8 +581,11 @@ public static SecurityHandler getCurrentSecurityHandler()
public void logout(Authentication.User user)
{
LOG.debug("logout {}",user);
if (user == null)
return;

LoginService login_service=getLoginService();
if (login_service!=null)
if (login_service != null)
{
login_service.logout(user.getUserIdentity());
}
Original file line number Diff line number Diff line change
@@ -40,10 +40,9 @@ public String toString()
}

@Override
@Deprecated
public void logout()
{
SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
if (security!=null)
security.logout(this);
}

}
Original file line number Diff line number Diff line change
@@ -34,6 +34,8 @@

import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.LoggedOutAuthentication;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.server.Authentication;
@@ -66,7 +68,6 @@ public Authentication authenticate(ServletRequest request)
try
{
Authentication authentication = _authenticator.validateRequest(request,__deferredResponse,true);

if (authentication!=null && (authentication instanceof Authentication.User) && !(authentication instanceof Authentication.ResponseSent))
{
LoginService login_service= _authenticator.getLoginService();
@@ -131,6 +132,25 @@ public Authentication login(String username, Object password, ServletRequest req
}
return null;
}



@Override
public Authentication logout (ServletRequest request)
{
SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
if (security!=null)
{
security.logout(null);
if (_authenticator instanceof LoginAuthenticator)
{
((LoginAuthenticator)_authenticator).logout(request);
return new LoggedOutAuthentication((LoginAuthenticator)_authenticator);
}
}

return Authentication.UNAUTHENTICATED;
}

/* ------------------------------------------------------------ */
public Object getPreviousAssociation()
Original file line number Diff line number Diff line change
@@ -192,14 +192,30 @@ public UserIdentity login(String username, Object password, ServletRequest reque
UserIdentity user = super.login(username,password,request);
if (user!=null)
{

HttpSession session = ((HttpServletRequest)request).getSession(true);
Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
}
return user;
}




@Override
public void logout(ServletRequest request)
{
super.logout(request);
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpSession session = httpRequest.getSession(false);

if (session == null)
return;

//clean up session
session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
}

/* ------------------------------------------------------------ */
@Override
public void prepareRequest(ServletRequest request)
@@ -536,7 +552,8 @@ private boolean notIgnored(String name)
}

/* ------------------------------------------------------------ */
/** This Authentication represents a just completed Form authentication.
/**
* This Authentication represents a just completed Form authentication.
* Subsequent requests from the same user are authenticated by the presents
* of a {@link SessionAuthentication} instance in their session.
*/
Original file line number Diff line number Diff line change
@@ -40,6 +40,8 @@ public abstract class LoginAuthenticator implements Authenticator
protected LoginService _loginService;
protected IdentityService _identityService;
private boolean _renewSession;



protected LoginAuthenticator()
{
@@ -63,6 +65,19 @@ public UserIdentity login(String username, Object password, ServletRequest servl
return null;
}


public void logout (ServletRequest request)
{
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpSession session = httpRequest.getSession(false);
if (session == null)
return;

session.removeAttribute(Session.SESSION_CREATED_SECURE);
}



@Override
public void setConfiguration(AuthConfiguration configuration)
{
Original file line number Diff line number Diff line change
@@ -33,18 +33,25 @@
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.session.Session;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener

/**
* SessionAuthentication
*
* When a user has been successfully authenticated with some types
* of Authenticator, the Authenticator stashes a SessionAuthentication
* into a HttpSession to remember that the user is authenticated.
*
*/
public class SessionAuthentication extends AbstractUserAuthentication
implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
{
private static final Logger LOG = Log.getLogger(SessionAuthentication.class);

private static final long serialVersionUID = -4643200685888258706L;



public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";

private final String _name;
@@ -92,23 +99,12 @@ private void readObject(ObjectInputStream stream)
}

@Override
@Deprecated
public void logout()
{
if (_session!=null && _session.getAttribute(__J_AUTHENTICATED)!=null)
_session.removeAttribute(__J_AUTHENTICATED);

doLogout();
}

private void doLogout()
{
SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
if (security!=null)
security.logout(this);
if (_session!=null)
_session.removeAttribute(Session.SESSION_CREATED_SECURE);
}


@Override
public String toString()
{
@@ -118,7 +114,6 @@ public String toString()
@Override
public void sessionWillPassivate(HttpSessionEvent se)
{

}

@Override
@@ -131,18 +126,15 @@ public void sessionDidActivate(HttpSessionEvent se)
}

@Override
@Deprecated
public void valueBound(HttpSessionBindingEvent event)
{
if (_session==null)
{
_session=event.getSession();
}
}

@Override
@Deprecated
public void valueUnbound(HttpSessionBindingEvent event)
{
doLogout();
}

}
Original file line number Diff line number Diff line change
@@ -34,6 +34,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import javax.servlet.DispatcherType;
import javax.servlet.HttpConstraintElement;
import javax.servlet.HttpMethodConstraintElement;
import javax.servlet.ServletException;
@@ -61,6 +63,7 @@
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Password;
@@ -1053,6 +1056,174 @@ public void testFormNoCookies() throws Exception
assertThat(response, startsWith("HTTP/1.1 403"));
assertThat(response, containsString("!role"));
}


/**
* Test Request.login() Request.logout() with FORM authenticator
* @throws Exception
*/
@Test
public void testFormProgrammaticLoginLogout() throws Exception
{
//Test programmatic login/logout within same request:
// login - perform programmatic login that should succeed, next request should be also logged in
// loginfail - perform programmatic login that should fail, next request should not be logged in
// loginfaillogin - perform programmatic login that should fail then another that succeeds, next request should be logged in
// loginlogin - perform successful login then try another that should fail, next request should be logged in
// loginlogout - perform successful login then logout, next request should not be logged in
// loginlogoutlogin - perform successful login then logout then login successfully again, next request should be logged in
_security.setHandler(new ProgrammaticLoginRequestHandler());
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
_server.start();

String response;

//login
response = _connector.getResponse("GET /ctx/prog?action=login HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
assertThat(response, containsString("user=admin"));
_server.stop();

//loginfail
_server.start();
response = _connector.getResponse("GET /ctx/prog?action=loginfail HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 500 Server Error"));
if (response.contains("JSESSIONID"))
{
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
}
else
response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n\r\n");

assertThat(response, not(containsString("user=admin")));
_server.stop();

//loginfaillogin
_server.start();
response = _connector.getResponse("GET /ctx/prog?action=loginfail HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 500 Server Error"));
response = _connector.getResponse("GET /ctx/prog?action=login HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
assertThat(response, containsString("user=admin"));
_server.stop();

//loginlogin
_server.start();
response = _connector.getResponse("GET /ctx/prog?action=loginlogin HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 500 Server Error"));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
assertThat(response, containsString("user=admin"));
_server.stop();

//loginlogout
_server.start();
response = _connector.getResponse("GET /ctx/prog?action=loginlogout HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
assertThat(response, containsString("user=null"));
_server.stop();

//loginlogoutlogin
_server.start();
response = _connector.getResponse("GET /ctx/prog?action=loginlogoutlogin HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
assertThat(response, containsString("user=user0"));
_server.stop();


//Test constraint-based login with programmatic login/logout:
// constraintlogin - perform constraint login, followed by programmatic login which should fail (already logged in)
_server.start();
response = _connector.getResponse("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response, containsString(" 302 Found"));
assertThat(response, containsString("/ctx/testLoginPage"));
assertThat(response, containsString("JSESSIONID="));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));

response = _connector.getResponse("GET /ctx/testLoginPage HTTP/1.0\r\n"+
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, containsString(" 200 OK"));
assertThat(response, not(containsString("JSESSIONID=" + session)));
response = _connector.getResponse("POST /ctx/j_security_check HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: 35\r\n" +
"\r\n" +
"j_username=user&j_password=password");
assertThat(response, startsWith("HTTP/1.1 302 "));
assertThat(response, containsString("Location"));
assertThat(response, containsString("/ctx/auth/info"));
assertThat(response, containsString("JSESSIONID="));
assertThat(response, not(containsString("JSESSIONID=" + session)));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponse("GET /ctx/prog?action=constraintlogin HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, startsWith("HTTP/1.1 500 Server Error"));
_server.stop();

// logout - perform constraint login, followed by programmatic logout, which should succeed
_server.start();
response = _connector.getResponse("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response, containsString(" 302 Found"));
assertThat(response, containsString("/ctx/testLoginPage"));
assertThat(response, containsString("JSESSIONID="));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));

response = _connector.getResponse("GET /ctx/testLoginPage HTTP/1.0\r\n"+
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, containsString(" 200 OK"));
assertThat(response, not(containsString("JSESSIONID=" + session)));
response = _connector.getResponse("POST /ctx/j_security_check HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: 35\r\n" +
"\r\n" +
"j_username=user&j_password=password");
assertThat(response, startsWith("HTTP/1.1 302 "));
assertThat(response, containsString("Location"));
assertThat(response, containsString("/ctx/auth/info"));
assertThat(response, containsString("JSESSIONID="));
assertThat(response, not(containsString("JSESSIONID=" + session)));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponse("GET /ctx/prog?action=logout HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, containsString(" 200 OK"));
response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response, containsString(" 200 OK"));
assertThat(response, containsString("user=null"));
}

@Test
public void testStrictBasic() throws Exception
@@ -1512,6 +1683,72 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
}
}

private class ProgrammaticLoginRequestHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException
{
baseRequest.setHandled(true);

String action = request.getParameter("action");
if (StringUtil.isBlank(action))
{
response.setStatus(200);
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().println("user="+request.getRemoteUser());
return;
}
else if ("login".equals(action))
{
request.login("admin", "password");
return;
}
else if ("loginfail".equals(action))
{
request.login("admin", "fail");
return;
}
else if ("loginfaillogin".equals(action))
{
try
{
request.login("admin", "fail");
}
catch (ServletException se)
{
request.login("admin", "password");
}
return;
}
else if ("loginlogin".equals(action))
{
request.login("admin", "password");
request.login("foo", "bar");
}
else if ("loginlogout".equals(action))
{
request.login("admin", "password");
request.logout();
}
else if ("loginlogoutlogin".equals(action))
{
request.login("admin", "password");
request.logout();
request.login("user0", "password");
}
else if ("constraintlogin".equals(action))
{
String user = request.getRemoteUser();
request.login("admin", "password");
}
else if ("logout".equals(action))
{
request.logout();
}
else
response.sendError(500);
}
}
private class RoleRefHandler extends HandlerWrapper
{
/* ------------------------------------------------------------ */
Original file line number Diff line number Diff line change
@@ -45,11 +45,12 @@ public Failed(String message)
/* ------------------------------------------------------------ */
/** A successful Authentication with User information.
*/
public interface User extends Authentication
public interface User extends LogoutAuthentication
{
String getAuthMethod();
UserIdentity getUserIdentity();
boolean isUserInRole(UserIdentity.Scope scope,String role);
@Deprecated
void logout();
}

@@ -63,16 +64,54 @@ public interface Wrapped extends Authentication
HttpServletResponse getHttpServletResponse();
}

/**
* An authentication that is capable of performing a programmatic login
* operation.
*
*/
public interface LoginAuthentication extends Authentication
{

/* ------------------------------------------------------------ */
/** Login with the LOGIN authenticator
* @param username the username
* @param password the password
* @param request the request
* @return The new Authentication state
*/
Authentication login(String username,Object password,ServletRequest request);
}


/**
* An authentication that is capable of performing a programmatic
* logout operation.
*
*/
public interface LogoutAuthentication extends Authentication
{
/* ------------------------------------------------------------ */
/**
* Remove any user information that may be present in the request
* such that a call to getUserPrincipal/getRemoteUser will return null.
*
* @param request the request
* @return NoAuthentication if we successfully logged out
*/
Authentication logout (ServletRequest request);
}


/* ------------------------------------------------------------ */
/** A deferred authentication with methods to progress
* the authentication process.
*/
public interface Deferred extends Authentication
public interface Deferred extends LoginAuthentication, LogoutAuthentication
{
/* ------------------------------------------------------------ */
/** Authenticate if possible without sending a challenge.
* This is used to check credentials that have been sent for
* non-manditory authentication.
* non-mandatory authentication.
* @param request the request
* @return The new Authentication state.
*/
@@ -81,22 +120,13 @@ public interface Deferred extends Authentication
/* ------------------------------------------------------------ */
/** Authenticate and possibly send a challenge.
* This is used to initiate authentication for previously
* non-manditory authentication.
* non-mandatory authentication.
* @param request the request
* @param response the response
* @return The new Authentication state.
*/
Authentication authenticate(ServletRequest request,ServletResponse response);


/* ------------------------------------------------------------ */
/** Login with the LOGIN authenticator
* @param username the username
* @param password the password
* @param request the request
* @return The new Authentication state
*/
Authentication login(String username,Object password,ServletRequest request);

}


@@ -127,6 +157,14 @@ public interface Failure extends ResponseSent
public interface SendSuccess extends ResponseSent
{
}

/* ------------------------------------------------------------ */
/** After a logout, the authentication reverts to a state
* where it is possible to programmatically log in again.
*/
public interface NonAuthenticated extends LoginAuthentication
{
}

/* ------------------------------------------------------------ */
/** Unauthenticated state.
13 changes: 7 additions & 6 deletions jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
Original file line number Diff line number Diff line change
@@ -2432,11 +2432,13 @@ private MultiParts newMultiParts(ServletInputStream inputStream, String contentT
@Override
public void login(String username, String password) throws ServletException
{
if (_authentication instanceof Authentication.Deferred)
if (_authentication instanceof Authentication.LoginAuthentication)
{
_authentication=((Authentication.Deferred)_authentication).login(username,password,this);
if (_authentication == null)
Authentication auth = ((Authentication.LoginAuthentication)_authentication).login(username,password,this);
if (auth == null)
throw new Authentication.Failed("Authentication failed for username '"+username+"'");
else
_authentication = auth;
}
else
{
@@ -2448,9 +2450,8 @@ public void login(String username, String password) throws ServletException
@Override
public void logout() throws ServletException
{
if (_authentication instanceof Authentication.User)
((Authentication.User)_authentication).logout();
_authentication=Authentication.UNAUTHENTICATED;
if (_authentication instanceof Authentication.LogoutAuthentication)
_authentication = ((Authentication.LogoutAuthentication)_authentication).logout(this);
}

/* ------------------------------------------------------------ */

0 comments on commit 432fc41

Please sign in to comment.