]> granicus.if.org Git - ejabberd/commitdiff
Check redirect_uri for OAUTH implicit grant
authorAlexey Shchepin <alexey@process-one.net>
Thu, 3 Oct 2019 03:18:07 +0000 (06:18 +0300)
committerAlexey Shchepin <alexey@process-one.net>
Thu, 3 Oct 2019 03:18:48 +0000 (06:18 +0300)
include/ejabberd_oauth.hrl
sql/lite.sql
sql/mysql.new.sql
sql/mysql.sql
sql/pg.new.sql
sql/pg.sql
src/ejabberd_oauth.erl
src/ejabberd_oauth_rest.erl
src/ejabberd_oauth_sql.erl

index 9254da1e53bf6da0513a6d12a676d6d247bdbf96..2daee39d9f9420fcf44c64263f84298c5912bf09 100644 (file)
@@ -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()] | '_'
          }).
index c77922c2088cdfcd521c662c5f260c0ad823ea6b..0b6bb12c184f183740a0afdb05acc116b16ea357 100644 (file)
@@ -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
 );
index b2f9acfd4ec26dff507282e8bf1ef226c082bb07..15843fc7d3cf62abe3fdd662cb716f8fece50963 100644 (file)
@@ -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,
index 7f415a2e465c2c2ac5063071203a651d98a487aa..7afc2cf1a74cea5c03439f5d53247fbdc7636eb5 100644 (file)
@@ -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;
index c585fd3074b39803b6e1646bab561715df399782..1a3d523163cb47bb8833f2b5c672c9a61ba63b19 100644 (file)
@@ -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,
index 0f87fd5d820385c7ca6a94d6d24bcbfe348f1211..34d9a4867fb030581ac4b5557225c1e01e2b7fc2 100644 (file)
@@ -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
 );
index 4060b4b7b09d264c0f5d8f12878a1a5812db21d3..27e211f139830e3186698114c48a78f540842e25 100644 (file)
@@ -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").
 -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},
+                                {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},
-                                {secret, binary},
-                                {grant_type, 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) ->
index b15fc904b1e577187976a9f2086cf6a6d57d42f4..6af67996be3456b93c73518d2092e8d57704f906 100644 (file)
@@ -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 ->
index 724017af4e5fcd0c6a2f97a2f830a53169471fa7..fb91a8813df2cfb06eab4458a83dd3b79406e9ee 100644 (file)
@@ -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 ->