-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
/
DefaultCrumbIssuer.java
168 lines (143 loc) · 5.71 KB
/
DefaultCrumbIssuer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/**
* Copyright (c) 2008-2010 Yahoo! Inc.
* All rights reserved.
* The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
package hudson.security.csrf;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import hudson.Extension;
import jenkins.util.SystemProperties;
import hudson.Util;
import jenkins.model.Jenkins;
import hudson.model.ModelObject;
import javax.annotation.Nonnull;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import jenkins.security.HexStringConfidentialKey;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
/**
* A crumb issuing algorithm based on the request principal and the remote address.
*
* @author dty
*/
public class DefaultCrumbIssuer extends CrumbIssuer {
private transient MessageDigest md;
private boolean excludeClientIPFromCrumb;
@Restricted(NoExternalUse.class)
public static /* non-final: Groovy Console */ boolean EXCLUDE_SESSION_ID = SystemProperties.getBoolean(DefaultCrumbIssuer.class.getName() + ".EXCLUDE_SESSION_ID");
@DataBoundConstructor
public DefaultCrumbIssuer(boolean excludeClientIPFromCrumb) {
try {
this.md = MessageDigest.getInstance("MD5");
this.excludeClientIPFromCrumb = excludeClientIPFromCrumb;
} catch (NoSuchAlgorithmException e) {
this.md = null;
this.excludeClientIPFromCrumb = false;
LOGGER.log(Level.SEVERE, "Can't find MD5", e);
}
}
public boolean isExcludeClientIPFromCrumb() {
return this.excludeClientIPFromCrumb;
}
private Object readResolve() {
try {
this.md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
this.md = null;
LOGGER.log(Level.SEVERE, "Can't find MD5", e);
}
return this;
}
/**
* {@inheritDoc}
*/
@Override
protected synchronized String issueCrumb(ServletRequest request, String salt) {
if (request instanceof HttpServletRequest) {
if (md != null) {
HttpServletRequest req = (HttpServletRequest) request;
StringBuilder buffer = new StringBuilder();
Authentication a = Jenkins.getAuthentication();
buffer.append(a.getName());
buffer.append(';');
if (!isExcludeClientIPFromCrumb()) {
buffer.append(getClientIP(req));
}
if (!EXCLUDE_SESSION_ID) {
buffer.append(';');
buffer.append(getSessionId(req));
}
md.update(buffer.toString().getBytes());
return Util.toHexString(md.digest(salt.getBytes()));
}
}
return null;
}
private String getSessionId(@Nonnull HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return "NO_SESSION";
}
return session.getId();
}
/**
* {@inheritDoc}
*/
@Override
public boolean validateCrumb(ServletRequest request, String salt, String crumb) {
if (request instanceof HttpServletRequest) {
String newCrumb = issueCrumb(request, salt);
if ((newCrumb != null) && (crumb != null)) {
// String.equals() is not constant-time, but this is
return MessageDigest.isEqual(newCrumb.getBytes(Charset.forName("US-ASCII")),
crumb.getBytes(Charset.forName("US-ASCII")));
}
}
return false;
}
private static final String X_FORWARDED_FOR = "X-Forwarded-For";
private String getClientIP(HttpServletRequest req) {
String defaultAddress = req.getRemoteAddr();
String forwarded = req.getHeader(X_FORWARDED_FOR);
if (forwarded != null) {
String[] hopList = forwarded.split(",");
if (hopList.length >= 1) {
return hopList[0];
}
}
return defaultAddress;
}
@Extension @Symbol("standard")
public static final class DescriptorImpl extends CrumbIssuerDescriptor<DefaultCrumbIssuer> implements ModelObject {
private final static HexStringConfidentialKey CRUMB_SALT = new HexStringConfidentialKey(Jenkins.class,"crumbSalt",16);
public DescriptorImpl() {
super(CRUMB_SALT.get(), SystemProperties.getString("hudson.security.csrf.requestfield", CrumbIssuer.DEFAULT_CRUMB_NAME));
load();
}
@Override
public String getDisplayName() {
return Messages.DefaultCrumbIssuer_DisplayName();
}
@Override
public DefaultCrumbIssuer newInstance(StaplerRequest req, JSONObject formData) throws FormException {
if (req == null) {
// This state is prohibited according to the Javadoc of the super method.
throw new FormException("DefaultCrumbIssuer new instance method is called for null Stapler request. "
+ "Such call is prohibited.", "req");
}
return req.bindJSON(DefaultCrumbIssuer.class, formData);
}
}
private static final Logger LOGGER = Logger.getLogger(DefaultCrumbIssuer.class.getName());
}