-
Notifications
You must be signed in to change notification settings - Fork 10
/
eopenid_v2.erl
168 lines (133 loc) · 5.15 KB
/
eopenid_v2.erl
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-2009 Torbjorn Tornkvist
%%% See the (MIT) LICENSE file for licensing information.
%%%
%%% Created : 3 Apr 2008 by Torbjorn Tornkvist <tobbe@tornkvist.org>
%%% Re-worked : 7 Feb 2009 by Torbjorn Tornkvist <tobbe@tornkvist.org>
%%% Desc. : Implementation of OpenID v2.0
%%%
%%%-------------------------------------------------------------------
-module(eopenid_v2).
-export([discover/1
,discover/2
,normalize/1
]).
-ignore_xref([{discover,1}]).
-import(eopenid_lib,
[new/0
,in/3
,http_get/1
,http_get/2
,http_post/4
,http_path_norm/1
]).
-include("eopenid_typespecs.hrl").
-include("eopenid_debug.hrl").
-include_lib("xmerl/include/xmerl.hrl").
-include_lib("eunit/include/eunit.hrl").
%%% --------------------------------------------------------------------
%%% @spec discover(ClaimedId :: list()) -> {ok,dict()} | {Type,Error}.
%%%
%%% @doc Performs an OpenID discovery to find out the the
%%% provider and the (optional) delegated ID.
%%% Returns an updated Dict.
%%% @end
%%% --------------------------------------------------------------------
-spec discover( string() ) -> {ok,dict()} | {etype(),error()}.
discover(ClaimedId) when is_list(ClaimedId) ->
discover(ClaimedId, new()).
discover(ClaimedId, Dict0) when is_list(ClaimedId) ->
XrdsContent = "application/xrds+xml",
Hdrs = [{"Accept", XrdsContent}],
NormId = http_path_norm(ClaimedId),
Dict = in("openid2.claimed_id", ClaimedId, Dict0),
try
{ok, {{_,200,_}, RHdrs, Body}} = http_get(NormId, Hdrs),
case proplists:get_value("content-type", RHdrs) of
XrdsContent -> discover_xrds(Body);
Else -> discover_html(Body)
end
catch
_:Error ->
?edbg("discover failed: ~p~n", [erlang:get_stacktrace()]),
{error, Error}
end.
-define(xelem(N,As,C), #xmlElement{name = N,
attributes = As,
content = C}).
-define(xattr(N,V), #xmlAttribute{name = N,
value = V}).
-define(xtxt(Tag,Val), #xmlText{parents = [{Tag,_}|_],
value = Val}).
discover_xrds(Body) ->
{Xml,_} = xmerl_scan:string(Body),
Services = xmerl_xpath:string("//Service", Xml),
Sort = fun({A1,B1}=A,{A2,B2}=B) ->
lkup(priority,A1) =< lkup(priority,A2)
end,
lists:sort(Sort,[{parse_attributes(S),parse_body(S)} || S <- Services]).
parse_attributes(?xelem(_,Attributes,_)) ->
[{Name,Val} ||
?xattr(Name,Val) <- Attributes].
parse_body(?xelem(_,_,Exports)) ->
[{Tag,Val} ||
?xelem(_,_,Products) <- Exports,
?xtxt(Tag,Val) <- Products].
discover_html(Body) ->
tbd.
lkup(K,L) ->
lists:keyfind(K,1,L).
%%%
%%% Normalize the Claimed ID according to 7.2 of:
%%% http://openid.net/specs/openid-authentication-2_0.html
%%%
-define(is_xri_char(C), (C == $= orelse
C == $@ orelse
C == $+ orelse
C == $$ orelse
C == $! orelse
C == $( )).
normalize("xri://"++[H|T]=Rest) when ?is_xri_char(H) ->
[H|T];
normalize("http://"++Rest) ->
"http://"++http_normalize(Rest);
normalize("https://"++Rest) ->
"https://"++http_normalize(Rest);
normalize([X|_]=Rest) when ?is_xri_char(X) ->
Rest;
normalize(Id) ->
"http://"++http_normalize(Id).
http_normalize(Str0) ->
[Str|_] = string:tokens(Str0, "#"),
case string:tokens(Str,"/") of
[X] -> X++"/";
_ -> Str
end.
-ifdef(EUNIT).
%%%
%%% See:
%%% http://openid.net/specs/openid-authentication-2_0.html#normalization_example
%%%
normalize_test_() ->
[
% A URI with a missing scheme is normalized to a http URI
?_assertMatch("http://example.com/", normalize("example.com"))
% An empty path component is normalized to a slash
,?_assertMatch("http://example.com/", normalize("http://example.com"))
% https URIs remain https URIs
,?_assertMatch("https://example.com/", normalize("https://example.com/"))
% No trailing slash is added to non-empty path components
,?_assertMatch("http://example.com/user", normalize("http://example.com/user"))
% Trailing slashes are preserved on non-empty path components
,?_assertMatch("http://example.com/user/", normalize("http://example.com/user/"))
% Trailing slashes are preserved when the path is empty
,?_assertMatch("http://example.com/", normalize("http://example.com/"))
% Normalized XRIs start with a global context symbol
,?_assertMatch("=example", normalize("=example"))
% Normalized XRIs start with a global context symbol
,?_assertMatch("=example", normalize("xri://=example"))
% Fragments should be removed
,?_assertMatch("http://openid.net/specs/openid-authentication-2_0.html",
normalize("http://openid.net/specs/openid-authentication-2_0.html#normalization_example"))
].
-endif.