Permalink
Browse files

[TECH-1154] OAuth support

  • Loading branch information...
1 parent 5d7fa29 commit 9dc328f6292dbcd42cfe8d3d54b5a03b36fd3177 @cstar cstar committed Nov 8, 2010
Showing with 152 additions and 16 deletions.
  1. +68 −0 examples/http-oauth.xml.in
  2. +4 −0 include/ts_http.hrl
  3. +10 −7 src/tsung/ts_http.erl
  4. +17 −3 src/tsung/ts_http_common.erl
  5. +43 −5 src/tsung_controller/ts_config_http.erl
  6. +10 −1 tsung-1.0.dtd
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<!DOCTYPE tsung SYSTEM "@prefix@/share/@PACKAGE_NAME@/@DTD@">
+<tsung loglevel="debug" dumptraffic="true" version="1.0">
+
+ <clients>
+ <client host="localhost" weight="1" maxusers="1000" use_controller_vm="true"/>
+ </clients>
+
+ <servers>
+ <server host="127.0.0.1" port="8000" type="tcp"></server>
+ </servers>
+
+ <load>
+ <arrivalphase phase="1" duration="2" unit="second">
+ <users interarrival="1" unit="second"></users>
+ </arrivalphase>
+ </load>
+
+ <sessions>
+ <session name="http_test_1" probability="100" type="ts_http">
+ <!--1. fetching token from SSO
+ using tsung XPath dynvars, the access_token and secret are stored for future use.
+ Please note the <oauth/> element with the consumer information.
+ By default requests are signed with "HMAC-SHA1" so it's not necessary here.
+ -->
+ <request>
+ <dyn_variable name="access_token" xpath="/tokenCreationResponse/tokenInfo/token/text()"/>
+ <dyn_variable name="access_token_secret" xpath="/tokenCreationResponse/tokenInfo/tokenSecret/text()" />
+ <http url="/token" method="POST" version="1.1" contents="rien">
+ <oauth consumer_key="key" consumer_secret="secret" method="HMAC-SHA1"/>
+ </http>
+ </request>
+
+ <!--2. access to protected resources
+ Using the variables set from the SSO, we are now able to access the protected resource
+ -->
+ <request subst="true">
+ <http url="/echo" method="GET" version="1.1">
+ <oauth consumer_key="key" consumer_secret="secret" access_token="%%_access_token%%" access_token_secret="%%_access_token_secret%%"/>
+ </http>
+ </request>
+
+ <!--3. refreshing token
+ refreshing the token work mostly as in step 1.
+ Values are overridden-->
+
+ <request subst="true">
+ <dyn_variable name="access_token" xpath="/tokenCreationResponse/tokenInfo/token/text()"/>
+ <dyn_variable name="access_token_secret" xpath="/tokenCreationResponse/tokenInfo/tokenSecret/text()" />
+ <http url="/token/%%_access_token%%" method="PUT" version="1.1">
+ <oauth consumer_key="key" consumer_secret="secret"/>
+ </http>
+ </request>
+
+ <!--4. access to protected resources
+ See step 2.-->
+
+ <request subst="true">
+ <http url="/echo" method="GET" version="1.1">
+ <oauth consumer_key="key" consumer_secret="secret" access_token="%%access_token_secret%%" access_token_secret="%%access_token_secret%%"/>
+ </http>
+ </request>
+ </session>
+</sessions>
+
+</tsung>
+
+
View
@@ -38,6 +38,10 @@
body = [],
id = 0,
user_agent,
+ oauth_consumer,
+ oauth_access_token,
+ oauth_access_secret,
+ oauth_url,
userid, % for www_authentication
passwd, % for www_authentication
soap_action % for SOAP support
View
@@ -106,13 +106,13 @@ parse_config(Element, Conf) ->
add_dynparams(false, DynData, Param, HostData) ->
add_dynparams(DynData#dyndata.proto, Param, HostData);
add_dynparams(true, DynData, OldReq=#http_request{url=OldUrl}, HostData) ->
- Req = subst(OldReq, DynData#dyndata.dynvars),
+ Req = subst(OldReq, DynData#dyndata.dynvars),
case Req#http_request.url of
- OldUrl ->
+ OldUrl ->
add_dynparams(DynData#dyndata.proto,Req, HostData);
"http" ++ _Rest -> % URL has changed and is absolute
URL=ts_config_http:parse_URL(Req#http_request.url),
- ?DebugF("URL dynamic subst: ~p~n",[URL]),
+ ?LOGF("URL dynamic subst: ~p~n",[URL],?WARN),
NewPort = ts_config_http:set_port(URL),
NewUrl = case OldUrl of
"http"++_ -> % old url absolute: useproxy must be true
@@ -121,17 +121,17 @@ add_dynparams(true, DynData, OldReq=#http_request{url=OldUrl}, HostData) ->
ts_config_http:set_query(URL)
end,
NewReq=add_dynparams(DynData#dyndata.proto,
- Req#http_request{url=NewUrl,host_header=undefined},
+ Req#http_request{url=NewUrl, host_header=undefined},
{URL#url.host, NewPort}),
{NewReq, {URL#url.host, NewPort,ts_config_http:set_scheme(URL#url.scheme)}};
_ -> % Same host:port
add_dynparams(DynData#dyndata.proto, Req, HostData)
end.
-%% Function: add_dynparams/3
+%% Function: add_dynparams/3
add_dynparams(DynData,Param=#http_request{host_header=undefined}, {Host,Port})->
Header=Host++":"++ integer_to_list(Port),
- ?DebugF("set host header dynamically: ~s~n",[Header]),
+ ?LOGF("set host header dynamically: ~s~n",[Header],?WARN),
add_dynparams(DynData, Param#http_request{host_header=Header},{Host,Port});
%% no cookies
add_dynparams(#http_dyndata{cookies=[],user_agent=UA},Param, _) ->
@@ -157,11 +157,14 @@ init_dynparams() ->
%% request parameters.
%% @end
%%----------------------------------------------------------------------
-subst(Req=#http_request{url=URL, body=Body, headers = Headers, userid=UserId, passwd=Passwd}, DynData) ->
+subst(Req=#http_request{url=URL, body=Body, headers = Headers,oauth_url=OUrl, oauth_access_token=AToken, oauth_access_secret=ASecret, userid=UserId, passwd=Passwd}, DynData) ->
Req#http_request{url = ts_search:subst(URL, DynData),
body = ts_search:subst(Body, DynData),
headers = lists:foldl(fun ({Name, Value}, Result) ->
[{Name, ts_search:subst(Value, DynData)} | Result]
end, [], Headers),
+ oauth_access_token = ts_search:subst(AToken, DynData),
+ oauth_access_secret = ts_search:subst(ASecret, DynData),
+ oauth_url = ts_search:subst(OUrl, DynData),
userid = ts_search:subst(UserId, DynData),
passwd = ts_search:subst(Passwd, DynData)}.
@@ -59,12 +59,13 @@ http_get(Args) ->
http_no_body(Method,#http_request{url=URL, version=Version, cookie=Cookie,
headers=Headers, user_agent=UA,
get_ims_date=undefined, soap_action=SOAPAction,
- host_header=Host, userid=UserId, passwd=Passwd})->
+ host_header=Host, userid=UserId, passwd=Passwd}=Req)->
?DebugF("~p ~p~n",[Method,URL]),
R = list_to_binary([Method, " ", URL," ", "HTTP/", Version, ?CRLF,
set_header("Host",Host,Headers, ""),
set_header("User-Agent",UA,Headers, ?USER_AGENT),
authenticate(UserId,Passwd),
+ oauth_sign(Method,Req),
soap_action(SOAPAction),
set_cookie_header({Cookie, Host, URL}),
headers(Headers),
@@ -75,14 +76,15 @@ http_no_body(Method,#http_request{url=URL, version=Version, cookie=Cookie,
http_no_body(Method,#http_request{url=URL, version=Version, cookie=Cookie,
headers=Headers, user_agent=UA,
get_ims_date=Date, soap_action=SOAPAction,
- host_header=Host, userid=UserId, passwd=Passwd}) ->
+ host_header=Host, userid=UserId, passwd=Passwd}=Req) ->
?DebugF("~p ~p~n",[Method, URL]),
list_to_binary([Method, " ", URL," ", "HTTP/", Version, ?CRLF,
["If-Modified-Since: ", Date, ?CRLF],
set_header("Host",Host,Headers, ""),
set_header("User-Agent",UA,Headers, ?USER_AGENT),
soap_action(SOAPAction),
authenticate(UserId,Passwd),
+ oauth_sign(Method,Req),
set_cookie_header({Cookie, Host, URL}),
headers(Headers),
?CRLF]).
@@ -102,14 +104,15 @@ http_body(Method,#http_request{url=URL, version=Version,
user_agent=UA, soap_action=SOAPAction,
content_type=ContentType,
body=Content, host_header=Host,
- userid=UserId, passwd=Passwd}) ->
+ userid=UserId, passwd=Passwd}=Req) ->
ContentLength=integer_to_list(size(Content)),
?DebugF("Content Length of POST: ~p~n.", [ContentLength]),
H = [Method, " ", URL," ", "HTTP/", Version, ?CRLF,
set_header("Host",Host,Headers, ""),
set_header("User-Agent",UA,Headers, ?USER_AGENT),
authenticate(UserId,Passwd),
soap_action(SOAPAction),
+ oauth_sign(Method, Req),
set_cookie_header({Cookie, Host, URL}),
headers(Headers),
"Content-Type: ", ContentType, ?CRLF,
@@ -128,6 +131,17 @@ authenticate(UserId,Passwd)->
AuthStr = ts_utils:encode_base64(lists:append([UserId,":",Passwd])),
["Authorization: Basic ",AuthStr,?CRLF].
+oauth_sign(_, #http_request{oauth_consumer = undefined})->[];
+oauth_sign(Method, #http_request{url=URL,
+ oauth_consumer=Consumer,
+ oauth_access_token=AccessToken,
+ oauth_access_secret=AccessSecret,
+ oauth_url=ServerURL})->
+ UrlParams = oauth_uri:params_from_string(URL),
+ ?LOGF("Signing query with ~p~n",[ServerURL],?WARN),
+ Params = oauth:signed_params(Method, ServerURL, UrlParams, Consumer, AccessToken, AccessSecret),
+ ["Authorization: OAuth ", oauth_uri:params_to_header_string(Params),?CRLF].
+
%%----------------------------------------------------------------------
%% @spec set_header(Name::string, Val::string | undefined, Headers::List,
%% Default::string) -> list()
@@ -36,7 +36,7 @@
-include("ts_profile.hrl").
-include("ts_http.hrl").
-include("ts_config.hrl").
-
+
-include("xmerl.hrl").
%%----------------------------------------------------------------------
@@ -49,7 +49,7 @@
parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) ->
ts_config:parse(Element,Conf);
parse_config(Element = #xmlElement{name=http},
- Config=#config{curid = Id, session_tab = Tab, servers = Servers,
+ Config=#config{curid = Id, session_tab = Tab, servers = [Server|_] = Servers,
sessions = [CurS | _], dynvar=DynVar,
subst = SubstFlag, match=MatchRegExp}) ->
Version = ts_config:getAttr(string,Element#xmlElement.attributes, version, "1.1"),
@@ -96,17 +96,55 @@ parse_config(Element = #xmlElement{name=http},
Request2#http_request.headers),
Request3 = Request2#http_request{headers=Headers,cookie=Cookies},
%% HTTP Authentication
- Msg = case lists:keysearch(www_authenticate, #xmlElement.name,
+ Request4 = case lists:keysearch(www_authenticate, #xmlElement.name,
Element#xmlElement.content) of
{value, AuthEl=#xmlElement{} } ->
UserId= ts_config:getAttr(string,AuthEl#xmlElement.attributes,
userid, undefined),
Passwd= ts_config:getAttr(string,AuthEl#xmlElement.attributes,
passwd, undefined),
- NewReq=Request3#http_request{userid=UserId, passwd=Passwd},
+ Request3#http_request{userid=UserId, passwd=Passwd};
+ _ ->
+ Request3
+ end,
+ %% OAuth
+ Msg = case lists:keysearch(oauth, #xmlElement.name,
+ Element#xmlElement.content) of
+ {value, AuthEl2=#xmlElement{} } ->
+ ConsumerKey = ts_config:getAttr(string,AuthEl2#xmlElement.attributes,
+ consumer_key, undefined),
+ ConsumerSecret = ts_config:getAttr(string,AuthEl2#xmlElement.attributes,
+ consumer_secret, undefined),
+ AccessToken = ts_config:getAttr(string,AuthEl2#xmlElement.attributes,
+ access_token, []),
+ AccessTokenSecret = ts_config:getAttr(string,AuthEl2#xmlElement.attributes,
+ access_token_secret, []),
+ Encoding = ts_config:getAttr(string,AuthEl2#xmlElement.attributes,
+ encoding, "HMAC-SHA1"),
+ AbsoluteURL = case URL of
+ "http" ++ _Rest ->
+ hd(string:tokens(URL, "?"));
+ _Relative ->
+ server_to_url(Server) ++ hd(string:tokens(URL, "?"))
+ end,
+ SigEncoding = case Encoding of
+ "PLAINTEXT" ->
+ plaintext;
+ "HMAC-SHA1" ->
+ hmac_sha1;
+ "RSA-SHA1" ->
+ rsa_sha1
+ end,
+ ?LOGF("TOKENS : ~p ~p ~p", [AccessToken, URL, AbsoluteURL], ?WARN),
+ NewReq=Request4#http_request{oauth_access_token=AccessToken,
+ oauth_url=AbsoluteURL,
+ oauth_access_secret=AccessTokenSecret,
+ oauth_consumer={ConsumerKey,
+ ConsumerSecret,
+ SigEncoding}},
set_msg(NewReq, {SubstFlag, MatchRegExp, UseProxy, Servers, PreviousHTTPServer, Tab, CurS#session.id} );
_ ->
- set_msg(Request3, {SubstFlag, MatchRegExp, UseProxy, Servers, PreviousHTTPServer, Tab, CurS#session.id} )
+ set_msg(Request4, {SubstFlag, MatchRegExp, UseProxy, Servers, PreviousHTTPServer, Tab, CurS#session.id} )
end,
ts_config:mark_prev_req(Id-1, Tab, CurS),
ets:insert(Tab,{{CurS#session.id, Id},Msg#ts_request{endpage=true,
View
@@ -145,7 +145,7 @@ repeat | if | change_type )*>
<!ELEMENT transaction (request | setdynvars | thinktime | for | repeat)+>
<!ATTLIST transaction name NMTOKEN #REQUIRED>
-<!ELEMENT http ( www_authenticate?, soap?, http_header*, add_cookie*)>
+<!ELEMENT http (oauth?, www_authenticate?, soap?, http_header*, add_cookie*)>
<!ATTLIST http
contents CDATA #IMPLIED
contents_from_file CDATA #IMPLIED
@@ -184,6 +184,15 @@ repeat | if | change_type )*>
passwd CDATA #REQUIRED
userid CDATA #REQUIRED >
+<!ELEMENT oauth EMPTY >
+<!ATTLIST oauth
+ consumer_key CDATA #REQUIRED
+ consumer_secret CDATA #REQUIRED
+ access_token CDATA #IMPLIED
+ access_token_secret CDATA #IMPLIED
+ method (HMAC-SHA1 | PLAINTEXT | RSA-SHA1) "HMAC-SHA1"
+ >
+
<!ELEMENT jabber EMPTY >
<!ATTLIST jabber
ack (global | local | no_ack) #REQUIRED

0 comments on commit 9dc328f

Please sign in to comment.