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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2012, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt,
5 * Tom Huybrechts, Vincent Latombe
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25 package hudson.model;
26
27 import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
28 import com.thoughtworks.xstream.XStream;
29 import hudson.*;
30 import hudson.model.Descriptor.FormException;
31 import hudson.model.listeners.SaveableListener;
32 import hudson.security.ACL;
33 import hudson.security.AccessControlled;
34 import hudson.security.Permission;
35 import hudson.security.SecurityRealm;
36 import hudson.util.FormApply;
37 import hudson.util.RunList;
38 import hudson.util.XStream2;
39 import jenkins.model.Jenkins;
40 import net.sf.json.JSONObject;
41
42 import org.acegisecurity.Authentication;
43 import org.acegisecurity.GrantedAuthority;
44 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
45 import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
46 import org.acegisecurity.userdetails.UserDetails;
47 import org.acegisecurity.userdetails.UsernameNotFoundException;
48 import org.springframework.dao.DataAccessException;
49 import org.kohsuke.stapler.StaplerRequest;
50 import org.kohsuke.stapler.StaplerResponse;
51 import org.kohsuke.stapler.export.Exported;
52 import org.kohsuke.stapler.export.ExportedBean;
53 import org.apache.commons.io.filefilter.DirectoryFileFilter;
54 import org.kohsuke.stapler.interceptor.RequirePOST;
55
56 import javax.servlet.ServletException;
57 import javax.servlet.http.HttpServletResponse;
58 import java.io.File;
59 import java.io.IOException;
60 import java.io.FileFilter;
61 import java.util.ArrayList;
62 import java.util.Collection;
63 import java.util.Collections;
64 import java.util.Comparator;
65 import java.util.HashSet;
66 import java.util.Iterator;
67 import java.util.List;
68 import java.util.Locale;
69 import java.util.Map;
70 import java.util.Set;
71 import java.util.concurrent.ConcurrentHashMap;
72 import java.util.concurrent.ConcurrentMap;
73 import java.util.logging.Level;
74 import java.util.logging.Logger;
75 import javax.annotation.CheckForNull;
76 import javax.annotation.Nonnull;
77
78 /**
79 * Represents a user.
80 *
81 * <p>
82 * In Hudson, {@link User} objects are created in on-demand basis;
83 * for example, when a build is performed, its change log is computed
84 * and as a result commits from users who Hudson has never seen may be discovered.
85 * When this happens, new {@link User} object is created.
86 *
87 * <p>
88 * If the persisted record for an user exists, the information is loaded at
89 * that point, but if there's no such record, a fresh instance is created from
90 * thin air (this is where {@link UserPropertyDescriptor#newInstance(User)} is
91 * called to provide initial {@link UserProperty} objects.
92 *
93 * <p>
94 * Such newly created {@link User} objects will be simply GC-ed without
95 * ever leaving the persisted record, unless {@link User#save()} method
96 * is explicitly invoked (perhaps as a result of a browser submitting a
97 * configuration.)
98 *
99 *
100 * @author Kohsuke Kawaguchi
101 */
102 @ExportedBean
103 public class User extends AbstractModelObject implements AccessControlled, DescriptorByNameOwner, Saveable, Comparable<User> {
104
105 private transient final String id;
106
107 private volatile String fullName;
108
109 private volatile String description;
110
111 /**
112 * List of {@link UserProperty}s configured for this project.
113 */
114 @CopyOnWrite
115 private volatile List<UserProperty> properties = new ArrayList<UserProperty>();
116
117
118 private User(String id, String fullName) {
119 this.id = id;
120 this.fullName = fullName;
121 load();
122 }
123
124 public int compareTo(User that) {
125 return this.id.compareTo(that.id);
126 }
127
128 /**
129 * Loads the other data from disk if it's available.
130 */
131 private synchronized void load() {
132 properties.clear();
133
134 XmlFile config = getConfigFile();
135 try {
136 if(config.exists())
137 config.unmarshal(this);
138 } catch (IOException e) {
139 LOGGER.log(Level.SEVERE, "Failed to load "+config,e);
140 }
141
142 // remove nulls that have failed to load
143 for (Iterator<UserProperty> itr = properties.iterator(); itr.hasNext();) {
144 if(itr.next()==null)
145 itr.remove();
146 }
147
148 // allocate default instances if needed.
149 // doing so after load makes sure that newly added user properties do get reflected
150 for (UserPropertyDescriptor d : UserProperty.all()) {
151 if(getProperty(d.clazz)==null) {
152 UserProperty up = d.newInstance(this);
153 if(up!=null)
154 properties.add(up);
155 }
156 }
157
158 for (UserProperty p : properties)
159 p.setUser(this);
160 }
161
162 @Exported
163 public String getId() {
164 return id;
165 }
166
167 public String getUrl() {
168 return "user/"+Util.rawEncode(id);
169 }
170
171 public String getSearchUrl() {
172 return "/user/"+Util.rawEncode(id);
173 }
174
175 /**
176 * The URL of the user page.
177 */
178 @Exported(visibility=999)
179 public String getAbsoluteUrl() {
180 return Jenkins.getInstance().getRootUrl()+getUrl();
181 }
182
183 /**
184 * Gets the human readable name of this user.
185 * This is configurable by the user.
186 *
187 * @return
188 * never null.
189 */
190 @Exported(visibility=999)
191 public String getFullName() {
192 return fullName;
193 }
194
195 /**
196 * Sets the human readable name of thie user.
197 */
198 public void setFullName(String name) {
199 if(Util.fixEmptyAndTrim(name)==null) name=id;
200 this.fullName = name;
201 }
202
203 @Exported
204 public String getDescription() {
205 return description;
206 }
207
208 /**
209 * Gets the user properties configured for this user.
210 */
211 public Map<Descriptor<UserProperty>,UserProperty> getProperties() {
212 return Descriptor.toMap(properties);
213 }
214
215 /**
216 * Updates the user object by adding a property.
217 */
218 public synchronized void addProperty(UserProperty p) throws IOException {
219 UserProperty old = getProperty(p.getClass());
220 List<UserProperty> ps = new ArrayList<UserProperty>(properties);
221 if(old!=null)
222 ps.remove(old);
223 ps.add(p);
224 p.setUser(this);
225 properties = ps;
226 save();
227 }
228
229 /**
230 * List of all {@link UserProperty}s exposed primarily for the remoting API.
231 */
232 @Exported(name="property",inline=true)
233 public List<UserProperty> getAllProperties() {
234 return Collections.unmodifiableList(properties);
235 }
236
237 /**
238 * Gets the specific property, or null.
239 */
240 public <T extends UserProperty> T getProperty(Class<T> clazz) {
241 for (UserProperty p : properties) {
242 if(clazz.isInstance(p))
243 return clazz.cast(p);
244 }
245 return null;
246 }
247
248 /**
249 * Creates an {@link Authentication} object that represents this user.
250 *
251 * @since 1.419
252 */
253 public Authentication impersonate() {
254 try {
255 UserDetails u = Jenkins.getInstance().getSecurityRealm().loadUserByUsername(id);
256 return new UsernamePasswordAuthenticationToken(u.getUsername(), "", u.getAuthorities());
257 } catch (UsernameNotFoundException e) {
258 // ignore
259 } catch (DataAccessException e) {
260 // ignore
261 }
262 // TODO: use the stored GrantedAuthorities
263 return new UsernamePasswordAuthenticationToken(id, "",
264 new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY});
265 }
266
267 /**
268 * Accepts the new description.
269 */
270 public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
271 checkPermission(Jenkins.ADMINISTER);
272
273 description = req.getParameter("description");
274 save();
275
276 rsp.sendRedirect("."); // go to the top page
277 }
278
279 /**
280 * Gets the fallback "unknown" user instance.
281 * <p>
282 * This is used to avoid null {@link User} instance.
283 */
284 public static @Nonnull User getUnknown() {
285 return get("unknown");
286 }
287
288 /**
289 * Gets the {@link User} object by its id or full name.
290 *
291 * @param create
292 * If true, this method will never return null for valid input
293 * (by creating a new {@link User} object if none exists.)
294 * If false, this method will return null if {@link User} object
295 * with the given name doesn't exist.
296 * @deprecated use {@link User#get(String, boolean, java.util.Map)}
297 */
298 public static User get(String idOrFullName, boolean create) {
299 return get(idOrFullName, create, Collections.emptyMap());
300 }
301
302 /**
303 * Gets the {@link User} object by its id or full name.
304 *
305 * @param create
306 * If true, this method will never return null for valid input
307 * (by creating a new {@link User} object if none exists.)
308 * If false, this method will return null if {@link User} object
309 * with the given name doesn't exist.
310 *
311 * @param context
312 * contextual environment this user idOfFullName was retrieved from,
313 * that can help resolve the user ID
314 */
315 public static User get(String idOrFullName, boolean create, Map context) {
316
317 if(idOrFullName==null)
318 return null;
319
320 // sort resolvers by priority
321 List<CanonicalIdResolver> resolvers = new ArrayList<CanonicalIdResolver>(Jenkins.getInstance().getExtensionList(CanonicalIdResolver.class));
322 Collections.sort(resolvers);
323
324 String id = null;
325 for (CanonicalIdResolver resolver : resolvers) {
326 id = resolver.resolveCanonicalId(idOrFullName, context);
327 if (id != null) break;
328 }
329 // DefaultUserCanonicalIdResolver will always return a non-null id if all other CanonicalIdResolver failed
330
331 return getOrCreate(id, idOrFullName, create);
332 }
333
334 /**
335 * retrieve a user by its ID, and create a new one if requested
336 */
337 private static User getOrCreate(String id, String fullName, boolean create) {
338 String idkey = id.toLowerCase(Locale.ENGLISH);
339
340 User u = byName.get(idkey);
341 if (u==null && (create || getConfigFileFor(id).exists())) {
342 User tmp = new User(id, fullName);
343 User prev = byName.putIfAbsent(idkey, u = tmp);
344 if (prev!=null)
345 u = prev; // if some has already put a value in the map, use it
346 }
347 return u;
348 }
349
350 /**
351 * Gets the {@link User} object by its id or full name.
352 */
353 public static @Nonnull User get(String idOrFullName) {
354 return get(idOrFullName,true);
355 }
356
357 /**
358 * Gets the {@link User} object representing the currently logged-in user, or null
359 * if the current user is anonymous.
360 * @since 1.172
361 */
362 public static @CheckForNull User current() {
363 Authentication a = Jenkins.getAuthentication();
364 if(a instanceof AnonymousAuthenticationToken)
365 return null;
366 return get(a.getName());
367 }
368
369 private static volatile long lastScanned;
370
371 /**
372 * Gets all the users.
373 */
374 public static Collection<User> getAll() {
375 if(System.currentTimeMillis() -lastScanned>10000) {
376 // occasionally scan the file system to check new users
377 // whether we should do this only once at start up or not is debatable.
378 // set this right away to avoid another thread from doing the same thing while we do this.
379 // having two threads doing the work won't cause race condition, but it's waste of time.
380 lastScanned = System.currentTimeMillis();
381
382 File[] subdirs = getRootDir().listFiles((FileFilter)DirectoryFileFilter.INSTANCE);
383 if(subdirs==null) return Collections.emptyList(); // shall never happen
384
385 for (File subdir : subdirs)
386 if(new File(subdir,"config.xml").exists()) {
387 String name = subdir.getName();
388 User.getOrCreate(name, name, true);
389 }
390
391 lastScanned = System.currentTimeMillis();
392 }
393
394 ArrayList<User> r = new ArrayList<User>(byName.values());
395 Collections.sort(r,new Comparator<User>() {
396 public int compare(User o1, User o2) {
397 return o1.getId().compareToIgnoreCase(o2.getId());
398 }
399 });
400 return r;
401 }
402
403 /**
404 * Reloads the configuration from disk.
405 */
406 public static void reload() {
407 for( User u : byName.values() )
408 u.load();
409 }
410
411 /**
412 * Stop gap hack. Don't use it. To be removed in the trunk.
413 */
414 public static void clear() {
415 byName.clear();
416 }
417
418 /**
419 * Returns the user name.
420 */
421 public String getDisplayName() {
422 return getFullName();
423 }
424
425 /**
426 * Gets the list of {@link Build}s that include changes by this user,
427 * by the timestamp order.
428 *
429 * TODO: do we need some index for this?
430 */
431 @WithBridgeMethods(List.class)
432 public RunList getBuilds() {
433 List<AbstractBuild> r = new ArrayList<AbstractBuild>();
434 for (AbstractProject<?,?> p : Jenkins.getInstance().getAllItems(AbstractProject.class))
435 for (AbstractBuild<?,?> b : p.getBuilds().newBuilds())
436 if(b.hasParticipant(this))
437 r.add(b);
438 return RunList.fromRuns(r);
439 }
440
441 /**
442 * Gets all the {@link AbstractProject}s that this user has committed to.
443 * @since 1.191
444 */
445 public Set<AbstractProject<?,?>> getProjects() {
446 Set<AbstractProject<?,?>> r = new HashSet<AbstractProject<?,?>>();
447 for (AbstractProject<?,?> p : Jenkins.getInstance().getAllItems(AbstractProject.class))
448 if(p.hasParticipant(this))
449 r.add(p);
450 return r;
451 }
452
453 public @Override String toString() {
454 return fullName;
455 }
456
457 /**
458 * The file we save our configuration.
459 */
460 protected final XmlFile getConfigFile() {
461 return new XmlFile(XSTREAM,getConfigFileFor(id));
462 }
463
464 private static final File getConfigFileFor(String id) {
465 return new File(getRootDir(),id +"/config.xml");
466 }
467
468 /**
469 * Gets the directory where Hudson stores user information.
470 */
471 private static File getRootDir() {
472 return new File(Jenkins.getInstance().getRootDir(), "users");
473 }
474
475 /**
476 * Save the settings to a file.
477 */
478 public synchronized void save() throws IOException {
479 if(BulkChange.contains(this)) return;
480 getConfigFile().write(this);
481 SaveableListener.fireOnChange(this, getConfigFile());
482 }
483
484 /**
485 * Deletes the data directory and removes this user from Hudson.
486 *
487 * @throws IOException
488 * if we fail to delete.
489 */
490 public synchronized void delete() throws IOException {
491 byName.remove(id.toLowerCase(Locale.ENGLISH));
492 Util.deleteRecursive(new File(getRootDir(), id));
493 }
494
495 /**
496 * Exposed remote API.
497 */
498 public Api getApi() {
499 return new Api(this);
500 }
501
502 /**
503 * Accepts submission from the configuration page.
504 */
505 @RequirePOST
506 public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
507 checkPermission(Jenkins.ADMINISTER);
508
509 fullName = req.getParameter("fullName");
510 description = req.getParameter("description");
511
512 JSONObject json = req.getSubmittedForm();
513
514 List<UserProperty> props = new ArrayList<UserProperty>();
515 int i = 0;
516 for (UserPropertyDescriptor d : UserProperty.all()) {
517 UserProperty p = getProperty(d.clazz);
518
519 JSONObject o = json.optJSONObject("userProperty" + (i++));
520 if (o!=null) {
521 if (p != null) {
522 p = p.reconfigure(req, o);
523 } else {
524 p = d.newInstance(req, o);
525 }
526 p.setUser(this);
527 }
528
529 if (p!=null)
530 props.add(p);
531 }
532 this.properties = props;
533
534 save();
535
536 FormApply.success(".").generateResponse(req,rsp,this);
537 }
538
539 /**
540 * Deletes this user from Hudson.
541 */
542 @RequirePOST
543 public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
544 checkPermission(Jenkins.ADMINISTER);
545 if (id.equals(Jenkins.getAuthentication().getName())) {
546 rsp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Cannot delete self");
547 return;
548 }
549
550 delete();
551
552 rsp.sendRedirect2("../..");
553 }
554
555 public void doRssAll(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
556 rss(req, rsp, " all builds", RunList.fromRuns(getBuilds()), Run.FEED_ADAPTER);
557 }
558
559 public void doRssFailed(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
560 rss(req, rsp, " regression builds", RunList.fromRuns(getBuilds()).regressionOnly(), Run.FEED_ADAPTER);
561 }
562
563 public void doRssLatest(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
564 final List<Run> lastBuilds = new ArrayList<Run>();
565 for (final TopLevelItem item : Jenkins.getInstance().getItems()) {
566 if (!(item instanceof Job)) continue;
567 for (Run r = ((Job) item).getLastBuild(); r != null; r = r.getPreviousBuild()) {
568 if (!(r instanceof AbstractBuild)) continue;
569 final AbstractBuild b = (AbstractBuild) r;
570 if (b.hasParticipant(this)) {
571 lastBuilds.add(b);
572 break;
573 }
574 }
575 }
576 rss(req, rsp, " latest build", RunList.fromRuns(lastBuilds), Run.FEED_ADAPTER_LATEST);
577 }
578
579 private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs, FeedAdapter adapter)
580 throws IOException, ServletException {
581 RSS.forwardToRss(getDisplayName()+ suffix, getUrl(), runs.newBuilds(), adapter, req, rsp);
582 }
583
584 /**
585 * Keyed by {@link User#id}. This map is used to ensure
586 * singleton-per-id semantics of {@link User} objects.
587 *
588 * The key needs to be lower cased for case insensitivity.
589 */
590 private static final ConcurrentMap<String,User> byName = new ConcurrentHashMap<String, User>();
591
592 /**
593 * Used to load/save user configuration.
594 */
595 public static final XStream2 XSTREAM = new XStream2();
596
597 private static final Logger LOGGER = Logger.getLogger(User.class.getName());
598
599 static {
600 XSTREAM.alias("user",User.class);
601 }
602
603 public ACL getACL() {
604 final ACL base = Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
605 // always allow a non-anonymous user full control of himself.
606 return new ACL() {
607 public boolean hasPermission(Authentication a, Permission permission) {
608 return (a.getName().equals(id) && !(a instanceof AnonymousAuthenticationToken))
609 || base.hasPermission(a, permission);
610 }
611 };
612 }
613
614 public void checkPermission(Permission permission) {
615 getACL().checkPermission(permission);
616 }
617
618 public boolean hasPermission(Permission permission) {
619 return getACL().hasPermission(permission);
620 }
621
622 /**
623 * With ADMINISTER permission, can delete users with persisted data but can't delete self.
624 */
625 public boolean canDelete() {
626 return hasPermission(Jenkins.ADMINISTER) && !id.equals(Jenkins.getAuthentication().getName())
627 && new File(getRootDir(), id).exists();
628 }
629
630 /**
631 * Checks for authorities (groups) associated with this user.
632 * If the caller lacks {@link Jenkins#ADMINISTER}, or any problems arise, returns an empty list.
633 * {@link SecurityRealm#AUTHENTICATED_AUTHORITY} and the username, if present, are omitted.
634 * @since 1.498
635 * @return a possibly empty list
636 */
637 public @Nonnull List<String> getAuthorities() {
638 if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) {
639 return Collections.emptyList();
640 }
641 List<String> r = new ArrayList<String>();
642 for (GrantedAuthority a : impersonate().getAuthorities()) {
643 if (a.equals(SecurityRealm.AUTHENTICATED_AUTHORITY)) {
644 continue;
645 }
646 String n = a.getAuthority();
647 if (n != null && !n.equals(id)) {
648 r.add(n);
649 }
650 }
651 return r;
652 }
653
654 public Descriptor getDescriptorByName(String className) {
655 return Jenkins.getInstance().getDescriptorByName(className);
656 }
657
658 public Object getDynamic(String token) {
659 for(Action action: getTransientActions()){
660 if(action.getUrlName().equals(token))
661 return action;
662 }
663 for(Action action: getPropertyActions()){
664 if(action.getUrlName().equals(token))
665 return action;
666 }
667 return null;
668 }
669
670 /**
671 * Return all properties that are also actions.
672 *
673 * @return the list can be empty but never null. read only.
674 */
675 public List<Action> getPropertyActions() {
676 List<Action> actions = new ArrayList<Action>();
677 for (UserProperty userProp : getProperties().values()) {
678 if (userProp instanceof Action) {
679 actions.add((Action) userProp);
680 }
681 }
682 return Collections.unmodifiableList(actions);
683 }
684
685 /**
686 * Return all transient actions associated with this user.
687 *
688 * @return the list can be empty but never null. read only.
689 */
690 public List<Action> getTransientActions() {
691 List<Action> actions = new ArrayList<Action>();
692 for (TransientUserActionFactory factory: TransientUserActionFactory.all()) {
693 actions.addAll(factory.createFor(this));
694 }
695 return Collections.unmodifiableList(actions);
696 }
697
698 public static abstract class CanonicalIdResolver extends AbstractDescribableImpl<CanonicalIdResolver> implements Comparable<CanonicalIdResolver> {
699
700 /**
701 * context key for realm (domain) where idOrFullName has been retreived from.
702 * Can be used (for example) to distinguish ambiguous committer ID using the SCM URL.
703 * Associated Value is a {@link String}
704 */
705 public static final String REALM = "realm";
706
707 public int compareTo(CanonicalIdResolver o) {
708 // reverse priority order
709 int i = getPriority();
710 int j = o.getPriority();
711 return i>j ? -1 : (i==j ? 0:1);
712 }
713
714 /**
715 * extract user ID from idOrFullName with help from contextual infos.
716 * can return <code>null</code> if no user ID matched the input
717 */
718 public abstract @CheckForNull String resolveCanonicalId(String idOrFullName, Map<String, ?> context);
719
720 public int getPriority() {
721 return 1;
722 }
723
724 }
725
726
727 /**
728 * Resolve user ID from full name
729 */
730 @Extension
731 public static class FullNameIdResolver extends CanonicalIdResolver {
732
733 @Override
734 public String resolveCanonicalId(String idOrFullName, Map<String, ?> context) {
735 for (User user : getAll()) {
736 if (idOrFullName.equals(user.getFullName())) return user.getId();
737 }
738 return null;
739 }
740
741 @Override
742 public int getPriority() {
743 return -1; // lower than default
744 }
745 }
746 }
747
748