Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
LinuxSuRen
jenkins
提交
578a2f5b
J
jenkins
项目概览
LinuxSuRen
/
jenkins
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
J
jenkins
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
578a2f5b
编写于
8月 05, 2011
作者:
K
Kohsuke Kawaguchi
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
[FIXED JENKINS-9363] added API token for REST API.
上级
29038528
变更
8
隐藏空白更改
内联
并排
Showing
8 changed file
with
335 addition
and
3 deletion
+335
-3
changelog.html
changelog.html
+3
-0
core/src/main/java/hudson/security/BasicAuthenticationFilter.java
.../main/java/hudson/security/BasicAuthenticationFilter.java
+19
-2
core/src/main/java/jenkins/security/ApiTokenFilter.java
core/src/main/java/jenkins/security/ApiTokenFilter.java
+66
-0
core/src/main/java/jenkins/security/ApiTokenProperty.java
core/src/main/java/jenkins/security/ApiTokenProperty.java
+128
-0
core/src/main/resources/jenkins/security/ApiTokenProperty/config.groovy
...resources/jenkins/security/ApiTokenProperty/config.groovy
+48
-0
core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken.html
...rces/jenkins/security/ApiTokenProperty/help-apiToken.html
+5
-0
test/src/main/java/jenkins/security/ApiTokenPropertyTest.java
.../src/main/java/jenkins/security/ApiTokenPropertyTest.java
+63
-0
war/src/main/webapp/WEB-INF/security/SecurityFilters.groovy
war/src/main/webapp/WEB-INF/security/SecurityFilters.groovy
+3
-1
未找到文件。
changelog.html
浏览文件 @
578a2f5b
...
...
@@ -60,6 +60,9 @@ Upcoming changes</a>
(
<a
href=
"https://issues.jenkins-ci.org/browse/JENKINS-10556"
>
issue 10556
</a>
)
<li
class=
rfe
>
Record and display who aborted builds.
<li
class=
rfe
>
Added API token support.
(
<a
href=
"https://issues.jenkins-ci.org/browse/JENKINS-9363"
>
issue 9363
</a>
)
</ul>
</div>
<!--=TRUNK-END=-->
...
...
core/src/main/java/hudson/security/BasicAuthenticationFilter.java
浏览文件 @
578a2f5b
...
...
@@ -23,8 +23,10 @@
*/
package
hudson.security
;
import
hudson.model.User
;
import
jenkins.model.Jenkins
;
import
hudson.util.Scrambler
;
import
jenkins.security.ApiTokenProperty
;
import
org.acegisecurity.context.SecurityContextHolder
;
import
org.kohsuke.stapler.StaplerRequest
;
import
org.kohsuke.stapler.StaplerResponse
;
...
...
@@ -46,9 +48,9 @@ import java.net.URLEncoder;
* Implements the dual authentcation mechanism.
*
* <p>
*
Hudson
supports both the HTTP basic authentication and the form-based authentication.
*
Jenkins
supports both the HTTP basic authentication and the form-based authentication.
* The former is for scripted clients, and the latter is for humans. Unfortunately,
* becase the servlet spec does not allow us to programatically authenticate users,
* beca
u
se the servlet spec does not allow us to programatically authenticate users,
* we need to rely on some hack to make it work, and this is the class that implements
* that hack.
*
...
...
@@ -131,6 +133,21 @@ public class BasicAuthenticationFilter implements Filter {
return
;
}
{
// attempt to authenticate as API token
User
u
=
User
.
get
(
username
);
ApiTokenProperty
t
=
u
.
getProperty
(
ApiTokenProperty
.
class
);
if
(
t
!=
null
&&
t
.
matchesPassword
(
password
))
{
SecurityContextHolder
.
getContext
().
setAuthentication
(
u
.
impersonate
());
try
{
chain
.
doFilter
(
request
,
response
);
}
finally
{
SecurityContextHolder
.
clearContext
();
}
return
;
}
}
path
=
req
.
getContextPath
()+
"/secured"
+
path
;
String
q
=
req
.
getQueryString
();
if
(
q
!=
null
)
...
...
core/src/main/java/jenkins/security/ApiTokenFilter.java
0 → 100644
浏览文件 @
578a2f5b
package
jenkins.security
;
import
hudson.model.User
;
import
hudson.util.Scrambler
;
import
org.acegisecurity.context.SecurityContextHolder
;
import
javax.servlet.Filter
;
import
javax.servlet.FilterChain
;
import
javax.servlet.FilterConfig
;
import
javax.servlet.ServletException
;
import
javax.servlet.ServletRequest
;
import
javax.servlet.ServletResponse
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
/**
* {@link Filter} that performs HTTP basic authentication based on API token.
*
* <p>
* Normally the filter chain would also contain another filter that handles BASIC
* auth with the real password. Care must be taken to ensure that this doesn't
* interfere with the other.
*
* @author Kohsuke Kawaguchi
*/
public
class
ApiTokenFilter
implements
Filter
{
public
void
init
(
FilterConfig
filterConfig
)
throws
ServletException
{
}
public
void
doFilter
(
ServletRequest
request
,
ServletResponse
response
,
FilterChain
chain
)
throws
IOException
,
ServletException
{
HttpServletRequest
req
=
(
HttpServletRequest
)
request
;
HttpServletResponse
rsp
=
(
HttpServletResponse
)
response
;
String
authorization
=
req
.
getHeader
(
"Authorization"
);
if
(
authorization
!=
null
)
{
// authenticate the user
String
uidpassword
=
Scrambler
.
descramble
(
authorization
.
substring
(
6
));
int
idx
=
uidpassword
.
indexOf
(
':'
);
if
(
idx
>=
0
)
{
String
username
=
uidpassword
.
substring
(
0
,
idx
);
String
password
=
uidpassword
.
substring
(
idx
+
1
);
// attempt to authenticate as API token
User
u
=
User
.
get
(
username
);
ApiTokenProperty
t
=
u
.
getProperty
(
ApiTokenProperty
.
class
);
if
(
t
!=
null
&&
t
.
matchesPassword
(
password
))
{
// even if we fail to match the password, we aren't rejecting it.
// as the user might be passing in a real password.
SecurityContextHolder
.
getContext
().
setAuthentication
(
u
.
impersonate
());
try
{
chain
.
doFilter
(
request
,
response
);
return
;
}
finally
{
SecurityContextHolder
.
clearContext
();
}
}
}
}
chain
.
doFilter
(
request
,
response
);
}
public
void
destroy
()
{
}
}
core/src/main/java/jenkins/security/ApiTokenProperty.java
0 → 100644
浏览文件 @
578a2f5b
/*
* 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.Functions
;
import
hudson.Util
;
import
hudson.model.Descriptor.FormException
;
import
hudson.model.User
;
import
hudson.model.UserProperty
;
import
hudson.model.UserPropertyDescriptor
;
import
hudson.util.FormValidation
;
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.
*/
private
ApiTokenProperty
(
String
seed
)
{
apiToken
=
Secret
.
fromString
(
seed
);
}
public
String
getApiToken
()
{
return
Util
.
getDigestOf
(
apiToken
.
getPlainText
());
}
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
"API Token"
;
}
/**
* 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 hasing the instance secret key + user ID.
*/
public
ApiTokenProperty
newInstance
(
User
user
)
{
return
new
ApiTokenProperty
(
Util
.
getDigestOf
(
Jenkins
.
getInstance
().
getSecretKey
()
+
":"
+
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
(
"<div>Updated</div>"
);
}
}
private
static
final
SecureRandom
RANDOM
=
new
SecureRandom
();
}
core/src/main/resources/jenkins/security/ApiTokenProperty/config.groovy
0 → 100644
浏览文件 @
578a2f5b
/*
* 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.ApiTokenProperty
;
f
=
namespace
(
lib
.
FormTagLib
)
f
.
advanced
(
title:
"Show API Token"
,
align:
"left"
)
{
f
.
entry
(
title:
_
(
"API Token"
),
field:
"apiToken"
)
{
f
.
readOnlyTextbox
(
id:
"apiToken"
)
// TODO: need to figure out the way to do this without using ID.
}
f
.
validateButton
(
title:
"Change API Token"
,
method:
"changeToken"
)
}
//f.entry(title:_("API Token"),field:"apiToken") {
//raw("""
//<a href="#" class='showDetails'>${_("Show API token")}</a><div style="display:none">
//""")
// f.readOnlyTextbox()
//raw("""
//</div>
//""")
//}
//
//f.validateButton(title:"") {
//
//}
//
core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken.html
0 → 100644
浏览文件 @
578a2f5b
<div>
This API token can be used for authenticating yourself in the REST API call.
See
<a
href=
"https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API"
>
our wiki
</a>
for more details.
The API token should be protected like your password, as it allows other people to access Jenkins as you.
</div>
\ No newline at end of file
test/src/main/java/jenkins/security/ApiTokenPropertyTest.java
0 → 100644
浏览文件 @
578a2f5b
package
jenkins.security
;
import
com.gargoylesoftware.htmlunit.HttpWebConnection
;
import
com.gargoylesoftware.htmlunit.html.HtmlForm
;
import
com.gargoylesoftware.htmlunit.html.HtmlPage
;
import
hudson.model.User
;
import
org.apache.commons.httpclient.Credentials
;
import
org.apache.commons.httpclient.HttpClient
;
import
org.apache.commons.httpclient.UsernamePasswordCredentials
;
import
org.apache.commons.httpclient.auth.AuthScheme
;
import
org.apache.commons.httpclient.auth.AuthScope
;
import
org.apache.commons.httpclient.auth.CredentialsNotAvailableException
;
import
org.apache.commons.httpclient.auth.CredentialsProvider
;
import
org.jvnet.hudson.test.HudsonTestCase
;
import
java.util.concurrent.Callable
;
/**
* @author Kohsuke Kawaguchi
*/
public
class
ApiTokenPropertyTest
extends
HudsonTestCase
{
/**
* Tests the UI interaction and authentication.
*/
public
void
testBasics
()
throws
Exception
{
jenkins
.
setSecurityRealm
(
createDummySecurityRealm
());
User
u
=
User
.
get
(
"foo"
);
ApiTokenProperty
t
=
u
.
getProperty
(
ApiTokenProperty
.
class
);
final
String
token
=
t
.
getApiToken
();
// make sure the UI shows the token
HtmlPage
config
=
createWebClient
().
goTo
(
u
.
getUrl
()
+
"/configure"
);
HtmlForm
form
=
config
.
getFormByName
(
"config"
);
assertEquals
(
token
,
form
.
getInputByName
(
"_.apiToken"
).
getValueAttribute
());
// round-trip shouldn't change the API token
submit
(
form
);
assertSame
(
t
,
u
.
getProperty
(
ApiTokenProperty
.
class
));
WebClient
wc
=
createWebClient
();
wc
.
setCredentialsProvider
(
new
CredentialsProvider
()
{
public
Credentials
getCredentials
(
AuthScheme
scheme
,
String
host
,
int
port
,
boolean
proxy
)
throws
CredentialsNotAvailableException
{
return
new
UsernamePasswordCredentials
(
"foo"
,
token
);
}
});
wc
.
setWebConnection
(
new
HttpWebConnection
(
wc
)
{
@Override
protected
HttpClient
getHttpClient
()
{
HttpClient
c
=
super
.
getHttpClient
();
c
.
getParams
().
setAuthenticationPreemptive
(
true
);
c
.
getState
().
setCredentials
(
new
AuthScope
(
"localhost"
,
localPort
,
AuthScope
.
ANY_REALM
),
new
UsernamePasswordCredentials
(
"foo"
,
token
));
return
c
;
}
});
// test the authentication
assertEquals
(
u
,
wc
.
executeOnServer
(
new
Callable
<
User
>()
{
public
User
call
()
throws
Exception
{
return
User
.
current
();
}
}));
}
}
war/src/main/webapp/WEB-INF/security/SecurityFilters.groovy
浏览文件 @
578a2f5b
...
...
@@ -40,6 +40,7 @@ import org.acegisecurity.ui.rememberme.RememberMeProcessingFilter
import
hudson.security.HttpSessionContextIntegrationFilter2
import
hudson.security.SecurityRealm
import
hudson.security.NoopFilter
import
jenkins.security.ApiTokenFilter
// providers that apply to both patterns
def
commonProviders
()
{
...
...
@@ -63,6 +64,7 @@ filter(ChainedServletFilter) {
// this persists the authentication across requests by using session
bean
(
HttpSessionContextIntegrationFilter2
)
{
},
bean
(
ApiTokenFilter
),
// allow clients to submit basic authentication credential
// but allow that to be skipped since it can interfere with reverse proxy setup
Boolean
.
getBoolean
(
"jenkins.security.ignoreBasicAuth"
)
?
bean
(
NoopFilter
)
:
...
...
@@ -73,7 +75,7 @@ filter(ChainedServletFilter) {
// since users of basic auth tends to be a program and won't see the redirection to the form
// page as a failure
authenticationEntryPoint
=
bean
(
BasicProcessingFilterEntryPoint
)
{
realmName
=
"
Hudson
"
realmName
=
"
Jenkins
"
}
},
bean
(
AuthenticationProcessingFilter2
)
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录