提交 79196b37 编写于 作者: J Jesse Glick

Merge branch 'login-events-JENKINS-20999'

......@@ -3,9 +3,12 @@ package hudson.security;
import groovy.lang.Binding;
import hudson.FilePath;
import hudson.cli.CLICommand;
import jenkins.model.Jenkins;
import hudson.remoting.Callable;
import hudson.util.spring.BeanBuilder;
import java.io.Console;
import java.io.IOException;
import jenkins.model.Jenkins;
import jenkins.security.SecurityListener;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationManager;
......@@ -19,9 +22,6 @@ import org.kohsuke.args4j.Option;
import org.springframework.dao.DataAccessException;
import org.springframework.web.context.WebApplicationContext;
import java.io.Console;
import java.io.IOException;
/**
* Partial implementation of {@link SecurityRealm} for username/password based authentication.
* This is a convenience base class if all you are trying to do is to check the given username
......@@ -76,7 +76,7 @@ public abstract class AbstractPasswordBasedSecurityRealm extends SecurityRealm i
if (password==null)
throw new BadCredentialsException("No password specified");
UserDetails d = AbstractPasswordBasedSecurityRealm.this.authenticate(userName, password);
UserDetails d = doAuthenticate(userName, password);
return new UsernamePasswordAuthenticationToken(d, password, d.getAuthorities());
}
};
......@@ -107,6 +107,17 @@ public abstract class AbstractPasswordBasedSecurityRealm extends SecurityRealm i
*/
protected abstract UserDetails authenticate(String username, String password) throws AuthenticationException;
private UserDetails doAuthenticate(String username, String password) throws AuthenticationException {
try {
UserDetails user = authenticate(username, password);
SecurityListener.fireAuthenticated(user);
return user;
} catch (AuthenticationException x) {
SecurityListener.fireFailedToAuthenticate(username);
throw x;
}
}
/**
* Retrieves information about an user by its name.
*
......@@ -132,7 +143,7 @@ public abstract class AbstractPasswordBasedSecurityRealm extends SecurityRealm i
}
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
return AbstractPasswordBasedSecurityRealm.this.authenticate(username,authentication.getCredentials().toString());
return doAuthenticate(username,authentication.getCredentials().toString());
}
}
......
......@@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import hudson.Util;
import jenkins.security.SecurityListener;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
......@@ -86,6 +87,7 @@ public class AuthenticationProcessingFilter2 extends AuthenticationProcessingFil
// (either when a redirect is issued, via its HttpResponseWrapper, or when the execution returns to its
// doFilter method.
request.getSession();
SecurityListener.fireLoggedIn(authResult.getName());
}
/**
......@@ -97,7 +99,11 @@ public class AuthenticationProcessingFilter2 extends AuthenticationProcessingFil
@Override
protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
super.onUnsuccessfulAuthentication(request, response, failed);
LOGGER.log(Level.INFO, "Login attempt failed", failed);
LOGGER.log(Level.FINE, "Login attempt failed", failed);
Authentication auth = failed.getAuthentication();
if (auth != null) {
SecurityListener.fireFailedToLogIn(auth.getName());
}
}
private static final Logger LOGGER = Logger.getLogger(AuthenticationProcessingFilter2.class.getName());
......
......@@ -298,6 +298,7 @@ import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import jenkins.security.SecurityListener;
/**
* Root object of the system.
......@@ -3038,6 +3039,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* @see BasicAuthenticationFilter
*/
public void doSecured( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
// TODO fire something in SecurityListener? (seems to be used only for REST calls when LegacySecurityRealm is active)
if(req.getUserPrincipal()==null) {
// authentication must have failed
rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
......@@ -3055,6 +3058,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* Called once the user logs in. Just forward to the top page.
* Used only by {@link LegacySecurityRealm}.
*/
public void doLoginEntry( StaplerRequest req, StaplerResponse rsp ) throws IOException {
if(req.getUserPrincipal()==null) {
......@@ -3062,6 +3066,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
return;
}
// TODO fire something in SecurityListener?
String from = req.getParameter("from");
if(from!=null && from.startsWith("/") && !from.equals("/loginError")) {
rsp.sendRedirect2(from); // I'm bit uncomfortable letting users redircted to other sites, make sure the URL falls into this domain
......@@ -3083,7 +3089,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* Logs out the user.
*/
public void doLogout( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
String user = getAuthentication().getName();
securityRealm.doLogout(req, rsp);
SecurityListener.fireLoggedOut(user);
}
/**
......
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security;
import hudson.ExtensionPoint;
import hudson.security.AbstractPasswordBasedSecurityRealm;
import hudson.security.SecurityRealm;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.userdetails.UserDetails;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Listener notified of various significant events related to security.
* @since TODO
*/
public abstract class SecurityListener implements ExtensionPoint {
private static final Logger LOGGER = Logger.getLogger(SecurityListener.class.getName());
/**
* Fired when a user was successfully authenticated by password.
* This might be via the web UI, or via REST (not with an API token) or CLI (not with an SSH key).
* Only {@link AbstractPasswordBasedSecurityRealm}s are considered.
* @param details details of the newly authenticated user, such as name and groups
*/
protected abstract void authenticated(@Nonnull UserDetails details);
/**
* Fired when a user tried to authenticate by password but failed.
* @param username the user
* @see #authenticated
*/
protected abstract void failedToAuthenticate(@Nonnull String username);
/**
* Fired when a user has logged in via the web UI.
* Would be called after {@link #authenticated}.
* @param username the user
*/
protected abstract void loggedIn(@Nonnull String username);
/**
* Fired when a user has failed to log in via the web UI.
* Would be called after {@link #failedToAuthenticate}.
* @param username the user
*/
protected abstract void failedToLogIn(@Nonnull String username);
/**
* Fired when a user logs out.
* @param username the user
*/
protected abstract void loggedOut(@Nonnull String username);
// TODO event for authenticated via SSH key in CLI (SshCliAuthenticator)
// TODO event for authenticated via API token (ApiTokenFilter)
// TODO event for permission denied exception thrown (mainly ACL.checkPermission), and/or caught at top level (ExceptionTranslationFilter.handleException)
// TODO event for new user signed up (e.g. in HudsonPrivateSecurityRealm)
// TODO event for CAPTCHA failure
@Restricted(NoExternalUse.class)
public static void fireAuthenticated(@Nonnull UserDetails details) {
if (LOGGER.isLoggable(Level.FINE)) {
List<String> groups = new ArrayList<String>();
for (GrantedAuthority auth : details.getAuthorities()) {
if (!auth.equals(SecurityRealm.AUTHENTICATED_AUTHORITY)) {
groups.add(auth.getAuthority());
}
}
LOGGER.log(Level.FINE, "authenticated: {0} {1}", new Object[] {details.getUsername(), groups});
}
for (SecurityListener l : all()) {
l.authenticated(details);
}
}
@Restricted(NoExternalUse.class)
public static void fireFailedToAuthenticate(@Nonnull String username) {
LOGGER.log(Level.FINE, "failed to authenticate: {0}", username);
for (SecurityListener l : all()) {
l.failedToAuthenticate(username);
}
}
@Restricted(NoExternalUse.class)
public static void fireLoggedIn(@Nonnull String username) {
LOGGER.log(Level.FINE, "logged in: {0}", username);
for (SecurityListener l : all()) {
l.loggedIn(username);
}
}
@Restricted(NoExternalUse.class)
public static void fireFailedToLogIn(@Nonnull String username) {
LOGGER.log(Level.FINE, "failed to log in: {0}", username);
for (SecurityListener l : all()) {
l.failedToLogIn(username);
}
}
@Restricted(NoExternalUse.class)
public static void fireLoggedOut(@Nonnull String username) {
LOGGER.log(Level.FINE, "logged out: {0}", username);
for (SecurityListener l : all()) {
l.loggedOut(username);
}
}
private static List<SecurityListener> all() {
return Jenkins.getInstance().getExtensionList(SecurityListener.class);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册