-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
/
ApiTokenProperty.java
137 lines (122 loc) · 5.02 KB
/
ApiTokenProperty.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
/*
* The MIT License
*
* Copyright (c) 2011, CloudBees, Inc.
*
* 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.Extension;
import hudson.Util;
import hudson.model.Descriptor.FormException;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
import hudson.util.HttpResponses;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.io.IOException;
import java.security.SecureRandom;
/**
* Remembers the API token for this user, that can be used like a password to login.
*
*
* @author Kohsuke Kawaguchi
* @see ApiTokenFilter
* @since 1.426
*/
public class ApiTokenProperty extends UserProperty {
private volatile Secret apiToken;
@DataBoundConstructor
public ApiTokenProperty() {
_changeApiToken();
}
/**
* We don't let the external code set the API token,
* but for the initial value of the token we need to compute the seed by ourselves.
*/
/*package*/ ApiTokenProperty(String seed) {
apiToken = Secret.fromString(seed);
}
public String getApiToken() {
String p = apiToken.getPlainText();
if (p.equals(Util.getDigestOf(Jenkins.getInstance().getSecretKey()+":"+user.getId()))) {
// if the current token is the initial value created by pre SECURITY-49 Jenkins, we can't use that.
// force using the newer value
apiToken = Secret.fromString(p=API_KEY_SEED.mac(user.getId()));
}
return Util.getDigestOf(p);
}
public boolean matchesPassword(String password) {
return getApiToken().equals(password);
}
public void changeApiToken() throws IOException {
_changeApiToken();
if (user!=null)
user.save();
}
private void _changeApiToken() {
byte[] random = new byte[16]; // 16x8=128bit worth of randomness, since we use md5 digest as the API token
RANDOM.nextBytes(random);
apiToken = Secret.fromString(Util.toHexString(random));
}
@Override
public UserProperty reconfigure(StaplerRequest req, JSONObject form) throws FormException {
return this;
}
@Extension
public static final class DescriptorImpl extends UserPropertyDescriptor {
public String getDisplayName() {
return Messages.ApiTokenProperty_DisplayName();
}
/**
* When we are creating a default {@link ApiTokenProperty} for User,
* we need to make sure it yields the same value for the same user,
* because there's no guarantee that the property is saved.
*
* But we also need to make sure that an attacker won't be able to guess
* the initial API token value. So we take the seed by hashing the secret + user ID.
*/
public ApiTokenProperty newInstance(User user) {
return new ApiTokenProperty(API_KEY_SEED.mac(user.getId()));
}
public HttpResponse doChangeToken(@AncestorInPath User u, StaplerResponse rsp) throws IOException {
ApiTokenProperty p = u.getProperty(ApiTokenProperty.class);
if (p==null) {
p = newInstance(u);
u.addProperty(p);
} else {
p.changeApiToken();
}
rsp.setHeader("script","document.getElementById('apiToken').value='"+p.getApiToken()+"'");
return HttpResponses.html(Messages.ApiTokenProperty_ChangeToken_Success());
}
}
private static final SecureRandom RANDOM = new SecureRandom();
/**
* We don't want an API key that's too long, so cut the length to 16 (which produces 32-letter MAC code in hexdump)
*/
private static final HMACConfidentialKey API_KEY_SEED = new HMACConfidentialKey(ApiTokenProperty.class,"seed",16);
}