mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-22 17:28:25 +01:00
Check redirect_uri for OAUTH implicit grant
This commit is contained in:
parent
949e71efb6
commit
5d549dca96
@ -26,8 +26,8 @@
|
||||
}).
|
||||
|
||||
-record(oauth_client, {
|
||||
client = <<"">> :: binary() | '_',
|
||||
secret = <<"">> :: binary() | '_',
|
||||
grant_type = password :: password | '_',
|
||||
client_id = <<"">> :: binary() | '_',
|
||||
client_name = <<"">> :: binary() | '_',
|
||||
grant_type :: password | implicit | '_',
|
||||
options :: [any()] | '_'
|
||||
}).
|
||||
|
@ -339,8 +339,8 @@ CREATE TABLE oauth_token (
|
||||
);
|
||||
|
||||
CREATE TABLE oauth_client (
|
||||
client text PRIMARY KEY,
|
||||
secret text NOT NULL,
|
||||
client_id text PRIMARY KEY,
|
||||
client_name text NOT NULL,
|
||||
grant_type text NOT NULL,
|
||||
options text NOT NULL
|
||||
);
|
||||
|
@ -384,6 +384,13 @@ CREATE TABLE oauth_token (
|
||||
expire bigint NOT NULL
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE oauth_client (
|
||||
client_id varchar(191) NOT NULL PRIMARY KEY,
|
||||
client_name text NOT NULL,
|
||||
grant_type text NOT NULL,
|
||||
options text NOT NULL
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE route (
|
||||
domain text NOT NULL,
|
||||
server_host varchar(191) NOT NULL,
|
||||
|
@ -355,8 +355,8 @@ CREATE TABLE oauth_token (
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE oauth_client (
|
||||
client varchar(191) NOT NULL PRIMARY KEY,
|
||||
secret text NOT NULL,
|
||||
client_id varchar(191) NOT NULL PRIMARY KEY,
|
||||
client_name text NOT NULL,
|
||||
grant_type text NOT NULL,
|
||||
options text NOT NULL
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
@ -529,6 +529,13 @@ CREATE TABLE oauth_token (
|
||||
|
||||
CREATE UNIQUE INDEX i_oauth_token_token ON oauth_token USING btree (token);
|
||||
|
||||
CREATE TABLE oauth_client (
|
||||
client_id text PRIMARY KEY,
|
||||
client_name text NOT NULL,
|
||||
grant_type text NOT NULL,
|
||||
options text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE route (
|
||||
domain text NOT NULL,
|
||||
server_host text NOT NULL,
|
||||
|
@ -359,8 +359,8 @@ CREATE TABLE oauth_token (
|
||||
CREATE UNIQUE INDEX i_oauth_token_token ON oauth_token USING btree (token);
|
||||
|
||||
CREATE TABLE oauth_client (
|
||||
client text PRIMARY KEY,
|
||||
secret text NOT NULL,
|
||||
client_id text PRIMARY KEY,
|
||||
client_name text NOT NULL,
|
||||
grant_type text NOT NULL,
|
||||
options text NOT NULL
|
||||
);
|
||||
|
@ -50,7 +50,9 @@
|
||||
|
||||
-export([get_commands_spec/0,
|
||||
oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1,
|
||||
oauth_add_client/3, oauth_remove_client/1]).
|
||||
oauth_add_client_password/3,
|
||||
oauth_add_client_implicit/3,
|
||||
oauth_remove_client/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
@ -65,6 +67,11 @@
|
||||
-callback lookup(binary()) -> {ok, #oauth_token{}} | error.
|
||||
-callback clean(non_neg_integer()) -> any().
|
||||
|
||||
-record(oauth_ctx, {
|
||||
password :: binary() | admin_generated,
|
||||
client :: #oauth_client{}
|
||||
}).
|
||||
|
||||
%% There are two ways to obtain an oauth token:
|
||||
%% * Using the web form/api results in the token being generated in behalf of the user providing the user/pass
|
||||
%% * Using the command line and oauth_issue_token command, the token is generated in behalf of ejabberd' sysadmin
|
||||
@ -99,12 +106,21 @@ get_commands_spec() ->
|
||||
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}},
|
||||
result_desc = "List of remaining tokens"
|
||||
},
|
||||
#ejabberd_commands{name = oauth_add_client, tags = [oauth],
|
||||
desc = "Add OAUTH client_id",
|
||||
module = ?MODULE, function = oauth_add_client,
|
||||
#ejabberd_commands{name = oauth_add_client_password, tags = [oauth],
|
||||
desc = "Add OAUTH client_id with password grant type",
|
||||
module = ?MODULE, function = oauth_add_client_password,
|
||||
args = [{client_id, binary},
|
||||
{secret, binary},
|
||||
{grant_type, binary}],
|
||||
{client_name, binary},
|
||||
{secret, binary}],
|
||||
policy = restricted,
|
||||
result = {res, restuple}
|
||||
},
|
||||
#ejabberd_commands{name = oauth_add_client_implicit, tags = [oauth],
|
||||
desc = "Add OAUTH client_id with implicit grant type",
|
||||
module = ?MODULE, function = oauth_add_client_implicit,
|
||||
args = [{client_id, binary},
|
||||
{client_name, binary},
|
||||
{redirect_uri, binary}],
|
||||
policy = restricted,
|
||||
result = {res, restuple}
|
||||
},
|
||||
@ -146,18 +162,21 @@ oauth_revoke_token(Token) ->
|
||||
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
|
||||
oauth_list_tokens().
|
||||
|
||||
oauth_add_client(Client, Secret, SGrantType) ->
|
||||
case SGrantType of
|
||||
<<"password">> ->
|
||||
DBMod = get_db_backend(),
|
||||
DBMod:store_client(#oauth_client{client = Client,
|
||||
secret = Secret,
|
||||
grant_type = password,
|
||||
options = []}),
|
||||
{ok, []};
|
||||
_ ->
|
||||
{error, "Unsupported grant type"}
|
||||
end.
|
||||
oauth_add_client_password(ClientID, ClientName, Secret) ->
|
||||
DBMod = get_db_backend(),
|
||||
DBMod:store_client(#oauth_client{client_id = ClientID,
|
||||
client_name = ClientName,
|
||||
grant_type = password,
|
||||
options = [{secret, Secret}]}),
|
||||
{ok, []}.
|
||||
|
||||
oauth_add_client_implicit(ClientID, ClientName, RedirectURI) ->
|
||||
DBMod = get_db_backend(),
|
||||
DBMod:store_client(#oauth_client{client_id = ClientID,
|
||||
client_name = ClientName,
|
||||
grant_type = implicit,
|
||||
options = [{redirect_uri, RedirectURI}]}),
|
||||
{ok, []}.
|
||||
|
||||
oauth_remove_client(Client) ->
|
||||
DBMod = get_db_backend(),
|
||||
@ -216,9 +235,23 @@ terminate(_Reason, _State) ->
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
|
||||
get_client_identity(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
|
||||
get_client_identity({client, ClientID}, Ctx) ->
|
||||
{ok, {Ctx, {client, ClientID}}}.
|
||||
|
||||
verify_redirection_uri(_, _, Ctx) -> {ok, Ctx}.
|
||||
verify_redirection_uri(_ClientID, RedirectURI, Ctx) ->
|
||||
case Ctx of
|
||||
#oauth_ctx{client = #oauth_client{grant_type = implicit} = Client} ->
|
||||
case get_redirect_uri(Client) of
|
||||
RedirectURI ->
|
||||
{ok, Ctx};
|
||||
_ ->
|
||||
{error, invalid_uri}
|
||||
end;
|
||||
#oauth_ctx{client = #oauth_client{}} ->
|
||||
{error, invalid_client};
|
||||
_ ->
|
||||
{ok, Ctx}
|
||||
end.
|
||||
|
||||
authenticate_user({User, Server}, Ctx) ->
|
||||
case jid:make(User, Server) of
|
||||
@ -228,15 +261,16 @@ authenticate_user({User, Server}, Ctx) ->
|
||||
case acl:match_rule(JID#jid.lserver, Access, JID) of
|
||||
allow ->
|
||||
case Ctx of
|
||||
{password, Password} ->
|
||||
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
|
||||
true ->
|
||||
#oauth_ctx{password = admin_generated} ->
|
||||
{ok, {Ctx, {user, User, Server}}};
|
||||
false ->
|
||||
{error, badpass}
|
||||
end;
|
||||
admin_generated ->
|
||||
{ok, {Ctx, {user, User, Server}}}
|
||||
#oauth_ctx{password = Password}
|
||||
when is_binary(Password) ->
|
||||
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
|
||||
true ->
|
||||
{ok, {Ctx, {user, User, Server}}};
|
||||
false ->
|
||||
{error, badpass}
|
||||
end
|
||||
end;
|
||||
deny ->
|
||||
{error, badpass}
|
||||
@ -245,7 +279,20 @@ authenticate_user({User, Server}, Ctx) ->
|
||||
{error, badpass}
|
||||
end.
|
||||
|
||||
authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
|
||||
authenticate_client(ClientID, Ctx) ->
|
||||
case ejabberd_option:oauth_client_id_check() of
|
||||
allow ->
|
||||
{ok, {Ctx, {client, ClientID}}};
|
||||
deny -> {error, not_allowed};
|
||||
db ->
|
||||
DBMod = get_db_backend(),
|
||||
case DBMod:lookup_client(ClientID) of
|
||||
{ok, #oauth_client{} = Client} ->
|
||||
{ok, {Ctx#oauth_ctx{client = Client}, {client, ClientID}}};
|
||||
_ ->
|
||||
{error, not_allowed}
|
||||
end
|
||||
end.
|
||||
|
||||
-spec verify_resowner_scope({user, binary(), binary()}, [binary()], any()) ->
|
||||
{ok, any(), [binary()]} | {error, any()}.
|
||||
@ -525,7 +572,7 @@ process(_Handlers,
|
||||
ClientId,
|
||||
RedirectURI,
|
||||
Scope,
|
||||
{password, Password}) of
|
||||
#oauth_ctx{password = Password}) of
|
||||
{ok, {_AppContext, Authorization}} ->
|
||||
{ok, {_AppContext2, Response}} =
|
||||
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
|
||||
@ -597,13 +644,18 @@ process(_Handlers,
|
||||
end,
|
||||
DBMod = get_db_backend(),
|
||||
case DBMod:lookup_client(ClientID) of
|
||||
{ok, #oauth_client{secret = Secret} = Client} ->
|
||||
case proplists:get_value(<<"grant_type">>, Q, <<"">>) of
|
||||
<<"password">> when
|
||||
Client#oauth_client.grant_type == password ->
|
||||
password;
|
||||
{ok, #oauth_client{grant_type = password} = Client} ->
|
||||
case get_client_secret(Client) of
|
||||
Secret ->
|
||||
case proplists:get_value(<<"grant_type">>, Q, <<"">>) of
|
||||
<<"password">> when
|
||||
Client#oauth_client.grant_type == password ->
|
||||
password;
|
||||
_ ->
|
||||
unsupported_grant_type
|
||||
end;
|
||||
_ ->
|
||||
unsupported_grant_type
|
||||
deny
|
||||
end;
|
||||
_ ->
|
||||
deny
|
||||
@ -623,7 +675,7 @@ process(_Handlers,
|
||||
end,
|
||||
case oauth2:authorize_password({Username, Server},
|
||||
Scope,
|
||||
{password, Password}) of
|
||||
#oauth_ctx{password = Password}) of
|
||||
{ok, {_AppContext, Authorization}} ->
|
||||
{ok, {_AppContext2, Response}} =
|
||||
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
|
||||
@ -663,6 +715,11 @@ get_db_backend() ->
|
||||
DBType = ejabberd_option:oauth_db_type(),
|
||||
list_to_existing_atom("ejabberd_oauth_" ++ atom_to_list(DBType)).
|
||||
|
||||
get_client_secret(#oauth_client{grant_type = password, options = Options}) ->
|
||||
proplists:get_value(secret, Options, false).
|
||||
|
||||
get_redirect_uri(#oauth_client{grant_type = implicit, options = Options}) ->
|
||||
proplists:get_value(redirect_uri, Options, false).
|
||||
|
||||
%% Headers as per RFC 6749
|
||||
json_response(Code, Body) ->
|
||||
|
@ -91,22 +91,25 @@ path(Path) ->
|
||||
Base = ejabberd_option:ext_api_path_oauth(),
|
||||
<<Base/binary, "/", Path/binary>>.
|
||||
|
||||
store_client(#oauth_client{client = Client,
|
||||
secret = Secret,
|
||||
grant_type = GrantType} = R) ->
|
||||
store_client(#oauth_client{client_id = ClientID,
|
||||
client_name = ClientName,
|
||||
grant_type = GrantType,
|
||||
options = Options} = R) ->
|
||||
Path = path(<<"store_client">>),
|
||||
%% Retry 2 times, with a backoff of 500millisec
|
||||
SGrantType =
|
||||
case GrantType of
|
||||
password -> <<"password">>
|
||||
password -> <<"password">>;
|
||||
implicit -> <<"implicit">>
|
||||
end,
|
||||
SOptions = misc:term_to_base64(Options),
|
||||
%% Retry 2 times, with a backoff of 500millisec
|
||||
case rest:with_retry(
|
||||
post,
|
||||
[ejabberd_config:get_myname(), Path, [],
|
||||
{[{<<"client">>, Client},
|
||||
{<<"secret">>, Secret},
|
||||
{[{<<"client_id">>, ClientID},
|
||||
{<<"client_name">>, ClientName},
|
||||
{<<"grant_type">>, SGrantType},
|
||||
{<<"options">>, []}
|
||||
{<<"options">>, SOptions}
|
||||
]}], 2, 500) of
|
||||
{ok, Code, _} when Code == 200 orelse Code == 201 ->
|
||||
ok;
|
||||
@ -115,22 +118,29 @@ store_client(#oauth_client{client = Client,
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
lookup_client(Client) ->
|
||||
lookup_client(ClientID) ->
|
||||
Path = path(<<"lookup_client">>),
|
||||
case rest:with_retry(post, [ejabberd_config:get_myname(), Path, [],
|
||||
{[{<<"client">>, Client}]}],
|
||||
{[{<<"client_id">>, ClientID}]}],
|
||||
2, 500) of
|
||||
{ok, 200, {Data}} ->
|
||||
Secret = proplists:get_value(<<"secret">>, Data, <<>>),
|
||||
ClientName = proplists:get_value(<<"client_name">>, Data, <<>>),
|
||||
SGrantType = proplists:get_value(<<"grant_type">>, Data, <<>>),
|
||||
GrantType =
|
||||
case SGrantType of
|
||||
<<"password">> -> password
|
||||
<<"password">> -> password;
|
||||
<<"implicit">> -> implicit
|
||||
end,
|
||||
{ok, #oauth_client{client = Client,
|
||||
secret = Secret,
|
||||
grant_type = GrantType,
|
||||
options = []}};
|
||||
SOptions = proplists:get_value(<<"options">>, Data, <<>>),
|
||||
case misc:base64_to_term(SOptions) of
|
||||
{term, Options} ->
|
||||
{ok, #oauth_client{client_id = ClientID,
|
||||
client_name = ClientName,
|
||||
grant_type = GrantType,
|
||||
options = Options}};
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
{ok, 404, _Resp} ->
|
||||
error;
|
||||
Other ->
|
||||
|
@ -83,37 +83,45 @@ clean(TS) ->
|
||||
ejabberd_config:get_myname(),
|
||||
?SQL("delete from oauth_token where expire < %(TS)d")).
|
||||
|
||||
lookup_client(Client) ->
|
||||
lookup_client(ClientID) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
ejabberd_config:get_myname(),
|
||||
?SQL("select @(secret)s, @(grant_type)s"
|
||||
" from oauth_client where client=%(Client)s")) of
|
||||
{selected, [{Secret, SGrantType}]} ->
|
||||
?SQL("select @(client_name)s, @(grant_type)s, @(options)s"
|
||||
" from oauth_client where client_id=%(ClientID)s")) of
|
||||
{selected, [{ClientName, SGrantType, SOptions}]} ->
|
||||
GrantType =
|
||||
case SGrantType of
|
||||
<<"password">> -> password
|
||||
<<"password">> -> password;
|
||||
<<"implicit">> -> implicit
|
||||
end,
|
||||
{ok, #oauth_client{client = Client,
|
||||
secret = Secret,
|
||||
grant_type = GrantType,
|
||||
options = []}};
|
||||
case misc:base64_to_term(SOptions) of
|
||||
{term, Options} ->
|
||||
{ok, #oauth_client{client_id = ClientID,
|
||||
client_name = ClientName,
|
||||
grant_type = GrantType,
|
||||
options = Options}};
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
store_client(#oauth_client{client = Client,
|
||||
secret = Secret,
|
||||
grant_type = GrantType}) ->
|
||||
store_client(#oauth_client{client_id = ClientID,
|
||||
client_name = ClientName,
|
||||
grant_type = GrantType,
|
||||
options = Options}) ->
|
||||
SGrantType =
|
||||
case GrantType of
|
||||
password -> <<"password">>
|
||||
password -> <<"password">>;
|
||||
implicit -> <<"implicit">>
|
||||
end,
|
||||
SOptions = <<"">>,
|
||||
SOptions = misc:term_to_base64(Options),
|
||||
case ?SQL_UPSERT(
|
||||
ejabberd_config:get_myname(),
|
||||
"oauth_client",
|
||||
["!client=%(Client)s",
|
||||
"secret=%(Secret)s",
|
||||
["!client_id=%(ClientID)s",
|
||||
"client_name=%(ClientName)s",
|
||||
"grant_type=%(SGrantType)s",
|
||||
"options=%(SOptions)s"]) of
|
||||
ok ->
|
||||
|
Loading…
Reference in New Issue
Block a user