File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\security\SecurityRealm.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.security;
25
26 import groovy.lang.Binding;
27 import hudson.ExtensionPoint;
28 import hudson.DescriptorExtensionList;
29 import hudson.Extension;
30 import hudson.cli.CLICommand;
31 import hudson.model.AbstractDescribableImpl;
32 import hudson.model.Descriptor;
33 import jenkins.model.Jenkins;
34 import hudson.security.FederatedLoginService.FederatedIdentity;
35 import hudson.security.captcha.CaptchaSupport;
36 import hudson.util.DescriptorList;
37 import hudson.util.PluginServletFilter;
38 import hudson.util.spring.BeanBuilder;
39 import org.acegisecurity.Authentication;
40 import org.acegisecurity.AuthenticationManager;
41 import org.acegisecurity.GrantedAuthorityImpl;
42 import org.acegisecurity.GrantedAuthority;
43 import org.acegisecurity.context.SecurityContext;
44 import org.acegisecurity.context.SecurityContextHolder;
45 import org.acegisecurity.ui.rememberme.RememberMeServices;
46 import static org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY;
47 import org.acegisecurity.userdetails.UserDetailsService;
48 import org.acegisecurity.userdetails.UserDetails;
49 import org.acegisecurity.userdetails.UsernameNotFoundException;
50 import org.kohsuke.stapler.HttpResponse;
51 import org.kohsuke.stapler.Stapler;
52 import org.kohsuke.stapler.StaplerRequest;
53 import org.kohsuke.stapler.StaplerResponse;
54 import org.springframework.context.ApplicationContext;
55 import org.springframework.web.context.WebApplicationContext;
56 import org.springframework.dao.DataAccessException;
57
58 import javax.servlet.Filter;
59 import javax.servlet.FilterConfig;
60 import javax.servlet.ServletException;
61 import javax.servlet.http.HttpSession;
62 import javax.servlet.http.Cookie;
63 import java.io.IOException;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.logging.Level;
67 import java.util.logging.Logger;
68
69 /**
70 * Pluggable security realm that connects external user database to Hudson.
71 *
72 * <p>
73 * If additional views/URLs need to be exposed,
74 * an active {@link SecurityRealm} is bound to <tt>CONTEXT_ROOT/securityRealm/</tt>
75 * through {@link jenkins.model.Jenkins#getSecurityRealm()}, so you can define additional pages and
76 * operations on your {@link SecurityRealm}.
77 *
78 * <h2>How do I implement this class?</h2>
79 * <p>
80 * For compatibility reasons, there are two somewhat different ways to implement a custom SecurityRealm.
81 *
82 * <p>
83 * One is to override the {@link #createSecurityComponents()} and create key Acegi components
84 * that control the authentication process.
85 * The default {@link SecurityRealm#createFilter(FilterConfig)} implementation then assembles them
86 * into a chain of {@link Filter}s. All the incoming requests to Hudson go through this filter chain,
87 * and when the filter chain is done, {@link SecurityContext#getAuthentication()} would tell us
88 * who the current user is.
89 *
90 * <p>
91 * If your {@link SecurityRealm} needs to touch the default {@link Filter} chain configuration
92 * (e.g., adding new ones), then you can also override {@link #createFilter(FilterConfig)} to do so.
93 *
94 * <p>
95 * This model is expected to fit most {@link SecurityRealm} implementations.
96 *
97 *
98 * <p>
99 * The other way of doing this is to ignore {@link #createSecurityComponents()} completely (by returning
100 * {@link SecurityComponents} created by the default constructor) and just concentrate on {@link #createFilter(FilterConfig)}.
101 * As long as the resulting filter chain properly sets up {@link Authentication} object at the end of the processing,
102 * Hudson doesn't really need you to fit the standard Acegi models like {@link AuthenticationManager} and
103 * {@link UserDetailsService}.
104 *
105 * <p>
106 * This model is for those "weird" implementations.
107 *
108 *
109 * <h2>Views</h2>
110 * <dl>
111 * <dt>loginLink.jelly</dt>
112 * <dd>
113 * This view renders the login link on the top right corner of every page, when the user
114 * is anonymous. For {@link SecurityRealm}s that support user sign-up, this is a good place
115 * to show a "sign up" link. See {@link HudsonPrivateSecurityRealm} implementation
116 * for an example of this.
117 *
118 * <dt>config.jelly</dt>
119 * <dd>
120 * This view is used to render the configuration page in the system config screen.
121 * </dl>
122 *
123 * @author Kohsuke Kawaguchi
124 * @since 1.160
125 * @see PluginServletFilter
126 */
127 public abstract class SecurityRealm extends AbstractDescribableImpl<SecurityRealm> implements ExtensionPoint {
128 /**
129 * Captcha Support to be used with this SecurityRealm for User Signup
130 */
131 private CaptchaSupport captchaSupport;
132
133 /**
134 * Creates fully-configured {@link AuthenticationManager} that performs authentication
135 * against the user realm. The implementation hides how such authentication manager
136 * is configured.
137 *
138 * <p>
139 * {@link AuthenticationManager} instantiation often depends on the user-specified parameters
140 * (for example, if the authentication is based on LDAP, the user needs to specify
141 * the host name of the LDAP server.) Such configuration is expected to be
142 * presented to the user via <tt>config.jelly</tt> and then
143 * captured as instance variables inside the {@link SecurityRealm} implementation.
144 *
145 * <p>
146 * Your {@link SecurityRealm} may also wants to alter {@link Filter} set up by
147 * overriding {@link #createFilter(FilterConfig)}.
148 */
149 public abstract SecurityComponents createSecurityComponents();
150
151 /**
152 * Creates a {@link CliAuthenticator} object that authenticates an invocation of a CLI command.
153 * See {@link CliAuthenticator} for more details.
154 *
155 * @param command
156 * The command about to be executed.
157 * @return
158 * never null. By default, this method returns a no-op authenticator that always authenticates
159 * the session as authenticated by the transport (which is often just {@link jenkins.model.Jenkins#ANONYMOUS}.)
160 */
161 public CliAuthenticator createCliAuthenticator(final CLICommand command) {
162 return new CliAuthenticator() {
163 public Authentication authenticate() {
164 return command.getTransportAuthentication();
165 }
166 };
167 }
168
169 /**
170 * {@inheritDoc}
171 *
172 * <p>
173 * {@link SecurityRealm} is a singleton resource in Hudson, and therefore
174 * it's always configured through <tt>config.jelly</tt> and never with
175 * <tt>global.jelly</tt>.
176 */
177 public Descriptor<SecurityRealm> getDescriptor() {
178 return super.getDescriptor();
179 }
180
181 /**
182 * Returns the URL to submit a form for the authentication.
183 * There's no need to override this, except for {@link LegacySecurityRealm}.
184 */
185 public String getAuthenticationGatewayUrl() {
186 return "j_acegi_security_check";
187 }
188
189 /**
190 * Gets the target URL of the "login" link.
191 * There's no need to override this, except for {@link LegacySecurityRealm}.
192 * On legacy implementation this should point to {@code loginEntry}, which
193 * is protected by <tt>web.xml</tt>, so that the user can be eventually authenticated
194 * by the container.
195 *
196 * <p>
197 * Path is relative from the context root of the Hudson application.
198 * The URL returned by this method will get the "from" query parameter indicating
199 * the page that the user was at.
200 */
201 public String getLoginUrl() {
202 return "login";
203 }
204
205 /**
206 * Returns true if this {@link SecurityRealm} supports explicit logout operation.
207 *
208 * <p>
209 * If the method returns false, "logout" link will not be displayed. This is useful
210 * when authentication doesn't require an explicit login activity (such as NTLM authentication
211 * or Kerberos authentication, where Hudson has no ability to log off the current user.)
212 *
213 * <p>
214 * By default, this method returns true.
215 *
216 * @since 1.307
217 */
218 public boolean canLogOut() {
219 return true;
220 }
221
222 /**
223 * Controls where the user is sent to after a logout. By default, it's the top page
224 * of Hudson, but you can return arbitrary URL.
225 *
226 * @param req
227 * {@link StaplerRequest} that represents the current request. Primarily so that
228 * you can get the context path. By the time this method is called, the session
229 * is already invalidated. Never null.
230 * @param auth
231 * The {@link Authentication} object that represents the user that was logging in.
232 * This parameter allows you to redirect people to different pages depending on who they are.
233 * @return
234 * never null.
235 * @since 1.314
236 * @see #doLogout(StaplerRequest, StaplerResponse)
237 */
238 protected String getPostLogOutUrl(StaplerRequest req, Authentication auth) {
239 return req.getContextPath()+"/";
240 }
241
242 public CaptchaSupport getCaptchaSupport() {
243 return captchaSupport;
244 }
245
246 public void setCaptchaSupport(CaptchaSupport captchaSupport) {
247 this.captchaSupport = captchaSupport;
248 }
249
250 public List<Descriptor<CaptchaSupport>> getCaptchaSupportDescriptors() {
251 return CaptchaSupport.all();
252 }
253
254 /**
255 * Handles the logout processing.
256 *
257 * <p>
258 * The default implementation erases the session and do a few other clean up, then
259 * redirect the user to the URL specified by {@link #getPostLogOutUrl(StaplerRequest, Authentication)}.
260 *
261 * @since 1.314
262 */
263 public void doLogout(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
264 HttpSession session = req.getSession(false);
265 if(session!=null)
266 session.invalidate();
267 Authentication auth = SecurityContextHolder.getContext().getAuthentication();
268 SecurityContextHolder.clearContext();
269
270 // reset remember-me cookie
271 Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,"");
272 cookie.setPath(req.getContextPath().length()>0 ? req.getContextPath() : "/");
273 rsp.addCookie(cookie);
274
275 rsp.sendRedirect2(getPostLogOutUrl(req,auth));
276 }
277
278 /**
279 * Returns true if this {@link SecurityRealm} allows online sign-up.
280 * This creates a hyperlink that redirects users to <tt>CONTEXT_ROOT/signUp</tt>,
281 * which will be served by the <tt>signup.jelly</tt> view of this class.
282 *
283 * <p>
284 * If the implementation needs to redirect the user to a different URL
285 * for signing up, use the following jelly script as <tt>signup.jelly</tt>
286 *
287 * <pre><xmp>
288 * <st:redirect url="http://www.sun.com/" xmlns:st="jelly:stapler"/>
289 * </xmp></pre>
290 */
291 public boolean allowsSignup() {
292 Class clz = getClass();
293 return clz.getClassLoader().getResource(clz.getName().replace('.','/')+"/signup.jelly")!=null;
294 }
295
296 /**
297 * Shortcut for {@link UserDetailsService#loadUserByUsername(String)}.
298 *
299 * @throws UserMayOrMayNotExistException
300 * If the security realm cannot even tell if the user exists or not.
301 * @return
302 * never null.
303 */
304 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
305 return getSecurityComponents().userDetails.loadUserByUsername(username);
306 }
307
308 /**
309 * If this {@link SecurityRealm} supports a look up of {@link GroupDetails} by their names, override this method
310 * to provide the look up.
311 *
312 * <p>
313 * This information, when available, can be used by {@link AuthorizationStrategy}s to improve the UI and
314 * error diagnostics for the user.
315 */
316 public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException {
317 throw new UserMayOrMayNotExistException(groupname);
318 }
319
320 /**
321 * Starts the user registration process for a new user that has the given verified identity.
322 *
323 * <p>
324 * If the user logs in through a {@link FederatedLoginService}, verified that the current user
325 * owns an {@linkplain FederatedIdentity identity}, but no existing user account has claimed that identity,
326 * then this method is invoked.
327 *
328 * <p>
329 * The expected behaviour is to confirm that the user would like to create a new account, and
330 * associate this federated identity to the newly created account (via {@link FederatedIdentity#addToCurrentUser()}.
331 *
332 * @throws UnsupportedOperationException
333 * If this implementation doesn't support the signup through this mechanism.
334 * This is the default implementation.
335 *
336 * @since 1.394
337 */
338 public HttpResponse commenceSignup(FederatedIdentity identity) {
339 throw new UnsupportedOperationException();
340 }
341
342 /**
343 * Generates a captcha image.
344 */
345 public final void doCaptcha(StaplerRequest req, StaplerResponse rsp) throws IOException {
346 if (captchaSupport != null) {
347 String id = req.getSession().getId();
348 rsp.setContentType("image/png");
349 rsp.addHeader("Cache-Control", "no-cache");
350 captchaSupport.generateImage(id, rsp.getOutputStream());
351 }
352 }
353
354 /**
355 * Validates the captcha.
356 */
357 protected final boolean validateCaptcha(String text) {
358 if (captchaSupport != null) {
359 String id = Stapler.getCurrentRequest().getSession().getId();
360 return captchaSupport.validateCaptcha(id, text);
361 }
362
363 // If no Captcha Support then bogus validation always returns true
364 return true;
365 }
366
367 /**
368 * Picks up the instance of the given type from the spring context.
369 * If there are multiple beans of the same type or if there are none,
370 * this method treats that as an {@link IllegalArgumentException}.
371 *
372 * This method is intended to be used to pick up a Acegi object from
373 * spring once the bean definition file is parsed.
374 */
375 public static <T> T findBean(Class<T> type, ApplicationContext context) {
376 Map m = context.getBeansOfType(type);
377 switch(m.size()) {
378 case 0:
379 throw new IllegalArgumentException("No beans of "+type+" are defined");
380 case 1:
381 return type.cast(m.values().iterator().next());
382 default:
383 throw new IllegalArgumentException("Multiple beans of "+type+" are defined: "+m);
384 }
385 }
386
387 /**
388 * Holder for the SecurityComponents.
389 */
390 private transient SecurityComponents securityComponents;
391
392 /**
393 * Use this function to get the security components, without necessarily
394 * recreating them.
395 */
396 public synchronized SecurityComponents getSecurityComponents() {
397 if (this.securityComponents == null) {
398 this.securityComponents = this.createSecurityComponents();
399 }
400 return this.securityComponents;
401 }
402
403 /**
404 * Creates {@link Filter} that all the incoming HTTP requests will go through
405 * for authentication.
406 *
407 * <p>
408 * The default implementation uses {@link #getSecurityComponents()} and builds
409 * a standard filter chain from /WEB-INF/security/SecurityFilters.groovy.
410 * But subclasses can override this to completely change the filter sequence.
411 *
412 * <p>
413 * For other plugins that want to contribute {@link Filter}, see
414 * {@link PluginServletFilter}.
415 *
416 * @since 1.271
417 */
418 public Filter createFilter(FilterConfig filterConfig) {
419 LOGGER.entering(SecurityRealm.class.getName(), "createFilter");
420
421 Binding binding = new Binding();
422 SecurityComponents sc = getSecurityComponents();
423 binding.setVariable("securityComponents", sc);
424 binding.setVariable("securityRealm",this);
425 BeanBuilder builder = new BeanBuilder();
426 builder.parse(filterConfig.getServletContext().getResourceAsStream("/WEB-INF/security/SecurityFilters.groovy"),binding);
427 WebApplicationContext context = builder.createApplicationContext();
428 return (Filter) context.getBean("filter");
429 }
430
431 /**
432 * Singleton constant that represents "no authentication."
433 */
434 public static final SecurityRealm NO_AUTHENTICATION = new None();
435
436 private static class None extends SecurityRealm {
437 public SecurityComponents createSecurityComponents() {
438 return new SecurityComponents(new AuthenticationManager() {
439 public Authentication authenticate(Authentication authentication) {
440 return authentication;
441 }
442 }, new UserDetailsService() {
443 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
444 throw new UsernameNotFoundException(username);
445 }
446 });
447 }
448
449 /**
450 * This special instance is not configurable explicitly,
451 * so it doesn't have a descriptor.
452 */
453 @Override
454 public Descriptor<SecurityRealm> getDescriptor() {
455 return null;
456 }
457
458 /**
459 * There's no group.
460 */
461 @Override
462 public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException {
463 throw new UsernameNotFoundException(groupname);
464 }
465
466 /**
467 * We don't need any filter for this {@link SecurityRealm}.
468 */
469 @Override
470 public Filter createFilter(FilterConfig filterConfig) {
471 return new ChainedServletFilter();
472 }
473
474 /**
475 * Maintain singleton semantics.
476 */
477 private Object readResolve() {
478 return NO_AUTHENTICATION;
479 }
480 }
481
482 /**
483 * Just a tuple so that we can create various inter-related security related objects and
484 * return them all at once.
485 *
486 * <p>
487 * None of the fields are ever null.
488 *
489 * @see SecurityRealm#createSecurityComponents()
490 */
491 public static final class SecurityComponents {
492 public final AuthenticationManager manager;
493 public final UserDetailsService userDetails;
494 public final RememberMeServices rememberMe;
495
496 public SecurityComponents() {
497 // we use AuthenticationManagerProxy here just as an implementation that fails all the time,
498 // not as a proxy. No one is supposed to use this as a proxy.
499 this(new AuthenticationManagerProxy());
500 }
501
502 public SecurityComponents(AuthenticationManager manager) {
503 // we use UserDetailsServiceProxy here just as an implementation that fails all the time,
504 // not as a proxy. No one is supposed to use this as a proxy.
505 this(manager,new UserDetailsServiceProxy());
506 }
507
508 public SecurityComponents(AuthenticationManager manager, UserDetailsService userDetails) {
509 this(manager,userDetails,createRememberMeService(userDetails));
510 }
511
512 public SecurityComponents(AuthenticationManager manager, UserDetailsService userDetails, RememberMeServices rememberMe) {
513 assert manager!=null && userDetails!=null && rememberMe!=null;
514 this.manager = manager;
515 this.userDetails = userDetails;
516 this.rememberMe = rememberMe;
517 }
518
519 @SuppressWarnings("deprecation")
520 private static RememberMeServices createRememberMeService(UserDetailsService uds) {
521 // create our default TokenBasedRememberMeServices, which depends on the availability of the secret key
522 TokenBasedRememberMeServices2 rms = new TokenBasedRememberMeServices2();
523 rms.setUserDetailsService(uds);
524 /*
525 TokenBasedRememberMeServices needs to be used in conjunction with RememberMeAuthenticationProvider,
526 and both needs to use the same key (this is a reflection of a poor design in AcgeiSecurity, if you ask me)
527 and various security plugins have its own groovy script that configures them.
528
529 So if we change this, it creates a painful situation for those plugins by forcing them to choose
530 to work with earlier version of Jenkins or newer version of Jenkins, and not both.
531
532 So we keep this here.
533 */
534 rms.setKey(Jenkins.getInstance().getSecretKey());
535 rms.setParameter("remember_me"); // this is the form field name in login.jelly
536 return rms;
537 }
538 }
539
540 /**
541 * All registered {@link SecurityRealm} implementations.
542 *
543 * @deprecated as of 1.286
544 * Use {@link #all()} for read access, and use {@link Extension} for registration.
545 */
546 public static final DescriptorList<SecurityRealm> LIST = new DescriptorList<SecurityRealm>(SecurityRealm.class);
547
548 /**
549 * Returns all the registered {@link SecurityRealm} descriptors.
550 */
551 public static DescriptorExtensionList<SecurityRealm,Descriptor<SecurityRealm>> all() {
552 return Jenkins.getInstance().<SecurityRealm,Descriptor<SecurityRealm>>getDescriptorList(SecurityRealm.class);
553 }
554
555
556 private static final Logger LOGGER = Logger.getLogger(SecurityRealm.class.getName());
557
558 /**
559 * {@link GrantedAuthority} that represents the built-in "authenticated" role, which is granted to
560 * anyone non-anonymous.
561 */
562 public static final GrantedAuthority AUTHENTICATED_AUTHORITY = new GrantedAuthorityImpl("authenticated");
563 }
564