77-module (oauth2_client ).
88-export ([get_access_token /2 , get_expiration_time /1 ,
99 refresh_access_token /2 ,
10+ get_jwks /1 ,
1011 get_oauth_provider /1 , get_oauth_provider /2 ,
11- get_openid_configuration /2 ,
12+ get_openid_configuration /1 ,
1213 build_openid_discovery_endpoint /3 ,
1314 merge_openid_configuration /2 ,
1415 merge_oauth_provider /2 ,
1516 extract_ssl_options_as_list /1 ,
16- format_ssl_options /1 , format_oauth_provider /1 , format_oauth_provider_id /1
17+ map_proxy_auth_to_httpc_option /1 ,
18+ map_proxy_to_httpc_option /1 ,
19+ map_ssl_options_to_httpc_option /1 ,
20+ map_timeout_to_httpc_option /1 ,
21+ format_ssl_options /1 , format_oauth_provider /1 , format_oauth_provider_id /1
1722 ]).
1823
1924-include (" oauth2_client.hrl" ).
@@ -25,29 +30,82 @@ get_access_token(OAuthProvider, Request) ->
2530 rabbit_log :debug (" get_access_token using OAuthProvider:~p and client_id:~p " ,
2631 [OAuthProvider , Request # access_token_request .client_id ]),
2732 URL = OAuthProvider # oauth_provider .token_endpoint ,
33+ Id = OAuthProvider # oauth_provider .id ,
2834 Header = [],
2935 Type = ? CONTENT_URLENCODED ,
3036 Body = build_access_token_request_body (Request ),
31- HTTPOptions = get_ssl_options_if_any (OAuthProvider ) ++
32- get_timeout_of_default (Request # access_token_request .timeout ),
33- Options = [],
34- Response = httpc :request (post , {URL , Header , Type , Body }, HTTPOptions , Options ),
37+ HTTPOptions =
38+ map_ssl_options_to_httpc_option (OAuthProvider # oauth_provider .ssl_options ) ++
39+ map_timeout_to_httpc_option (Request # access_token_request .timeout ),
40+ Response = http_post (Id , URL , Header , Type , Body , HTTPOptions ,
41+ OAuthProvider # oauth_provider .proxy_options ),
3542 parse_access_token_response (Response ).
3643
3744-spec refresh_access_token (oauth_provider (), refresh_token_request ()) ->
3845 {ok , successful_access_token_response ()} |
3946 {error , unsuccessful_access_token_response () | any ()}.
4047refresh_access_token (OAuthProvider , Request ) ->
48+ Id = OAuthProvider # oauth_provider .id ,
4149 URL = OAuthProvider # oauth_provider .token_endpoint ,
4250 Header = [],
4351 Type = ? CONTENT_URLENCODED ,
4452 Body = build_refresh_token_request_body (Request ),
45- HTTPOptions = get_ssl_options_if_any (OAuthProvider ) ++
46- get_timeout_of_default (Request # refresh_token_request .timeout ),
47- Options = [],
48- Response = httpc :request (post , {URL , Header , Type , Body }, HTTPOptions , Options ),
53+ HTTPOptions =
54+ map_ssl_options_to_httpc_option (OAuthProvider # oauth_provider .ssl_options ) ++
55+ map_timeout_to_httpc_option (Request # refresh_token_request .timeout ),
56+ Response = http_post (Id , URL , Header , Type , Body , HTTPOptions ,
57+ OAuthProvider # oauth_provider .proxy_options ),
4958 parse_access_token_response (Response ).
5059
60+ ensure_http_client_started (Id , ProxyOptions ) ->
61+ Profile = case Id of
62+ root -> root ;
63+ _ -> binary_to_atom (Id )
64+ end ,
65+ rabbit_log :debug (" Starting http client ~p ..." , [Profile ]),
66+ case inets :start (httpc , [{profile , Profile }]) of
67+ {ok , _ } ->
68+ HttpProxyOptions = map_proxy_to_httpc_option (ProxyOptions ),
69+ case ensure_http_proxy_options_if_any (Profile , HttpProxyOptions ) of
70+ ok -> {ok , Profile };
71+ {error , _ } = Error -> Error
72+ end ;
73+ {error , {already_started , _ }} ->
74+ {ok , Profile };
75+ Error ->
76+ rabbit_log :error (" Failed to start httpc client: ~p " , [Error ]),
77+ Error
78+ end .
79+ ensure_http_proxy_options_if_any (_Profile , []) ->
80+ ok ;
81+ ensure_http_proxy_options_if_any (Profile , HttpProxyOptions ) ->
82+ case httpc :set_options (HttpProxyOptions , Profile ) of
83+ ok ->
84+ rabbit_log :debug (" Successfully set_options ~p on http client ~p " ,
85+ [HttpProxyOptions , Profile ]),
86+ ok ;
87+ {error , _ } = Error -> Error
88+ end .
89+
90+ http_post (Id , URL , Header , Type , Body , HTTPOptions , ProxyOptions ) ->
91+ http_request (Id , post , {URL , Header , Type , Body }, HTTPOptions , ProxyOptions ).
92+ http_get (Id , URL , HTTPOptions , ProxyOptions ) ->
93+ http_request (Id , get , {URL , []}, HTTPOptions , ProxyOptions ).
94+ http_request (Id , Method , Payload , HTTPOptions , ProxyOptions ) ->
95+ case ensure_http_client_started (Id , ProxyOptions ) of
96+ {ok , Profile } ->
97+ case ProxyOptions of
98+ undefined ->
99+ httpc :request (Method , Payload , HTTPOptions , [], Profile );
100+ _ ->
101+ httpc :request (Method , Payload ,
102+ HTTPOptions ++ map_proxy_auth_to_httpc_option (ProxyOptions ),
103+ [],
104+ Profile )
105+ end ;
106+ {error , _ } = Error -> Error
107+ end .
108+
51109append_paths (Path1 , Path2 ) ->
52110 erlang :iolist_to_binary ([Path1 , Path2 ]).
53111
@@ -93,14 +151,27 @@ drop_trailing_path_separator(Path) when is_list(Path) ->
93151 _ -> Path
94152 end .
95153
96- -spec get_openid_configuration (DiscoveryEndpoint :: uri_string :uri_string (),
97- ssl :tls_option () | []) -> {ok , openid_configuration ()} | {error , term ()}.
98- get_openid_configuration (DiscoverEndpoint , TLSOptions ) ->
99- rabbit_log :debug (" get_openid_configuration from ~p (~p )" , [DiscoverEndpoint ,
100- format_ssl_options (TLSOptions )]),
101- Options = [],
102- Response = httpc :request (get , {DiscoverEndpoint , []}, TLSOptions , Options ),
154+ -spec get_openid_configuration (oauth_provider ()) -> {ok , openid_configuration ()} | {error , term ()}.
155+ get_openid_configuration (# oauth_provider {id = Id , discovery_endpoint = Endpoint ,
156+ ssl_options = SslOptions , proxy_options = ProxyOptions }) ->
157+ rabbit_log :debug (" get_openid_configuration from ~p (~p ) [~p ]" , [Endpoint ,
158+ format_ssl_options (SslOptions ), format_proxy_options (ProxyOptions )]),
159+ HTTPOptions =
160+ map_ssl_options_to_httpc_option (SslOptions ) ++
161+ map_timeout_to_httpc_option (? DEFAULT_HTTP_TIMEOUT ),
162+
163+ Response = http_get (Id , Endpoint , HTTPOptions , ProxyOptions ),
103164 parse_openid_configuration_response (Response ).
165+
166+ -spec get_jwks (oauth_provider ()) -> {ok , term ()} | {error , term ()}.
167+ get_jwks (# oauth_provider {id = Id , jwks_uri = JwksUrl ,
168+ ssl_options = SslOptions , proxy_options = ProxyOptions }) ->
169+ rabbit_log :debug (" get_jwks from ~p (~p ) [~p ]" , [JwksUrl ,
170+ format_ssl_options (SslOptions ), format_proxy_options (ProxyOptions )]),
171+ HTTPOptions =
172+ map_ssl_options_to_httpc_option (SslOptions ) ++
173+ map_timeout_to_httpc_option (? DEFAULT_HTTP_TIMEOUT ),
174+ http_get (Id , JwksUrl , HTTPOptions , ProxyOptions ).
104175
105176-spec merge_openid_configuration (openid_configuration (), oauth_provider ()) ->
106177 oauth_provider ().
@@ -283,7 +354,7 @@ download_oauth_provider(OAuthProvider) ->
283354 undefined -> {error , {missing_oauth_provider_attributes , [issuer ]}};
284355 URL ->
285356 rabbit_log :debug (" Downloading oauth_provider using ~p " , [URL ]),
286- case get_openid_configuration (URL , get_ssl_options_if_any ( OAuthProvider ) ) of
357+ case get_openid_configuration (OAuthProvider ) of
287358 {ok , OpenIdConfiguration } ->
288359 {ok , update_oauth_provider_endpoints_configuration (
289360 merge_openid_configuration (OpenIdConfiguration , OAuthProvider ))};
@@ -341,7 +412,6 @@ get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes)
341412 [OAuthProviderId , Error0 ]),
342413 Error0 ;
343414 Config ->
344- rabbit_log :debug (" Found oauth_provider configuration ~p " , [Config ]),
345415 OAuthProvider = map_to_oauth_provider (Config ),
346416 rabbit_log :debug (" Resolved oauth_provider ~p " , [format_oauth_provider (OAuthProvider )]),
347417 case find_missing_attributes (OAuthProvider , ListOfRequiredAttributes ) of
@@ -395,8 +465,36 @@ lookup_root_oauth_provider() ->
395465 token_endpoint = get_env (token_endpoint ),
396466 authorization_endpoint = get_env (authorization_endpoint ),
397467 end_session_endpoint = get_env (end_session_endpoint ),
398- ssl_options = extract_ssl_options_as_list (Map )
468+ ssl_options = extract_ssl_options_as_list (Map ),
469+ proxy_options = extract_proxy_options (get_env (proxy , []))
399470 }.
471+
472+ -spec extract_proxy_options (#{atom () => any ()}|list ()) -> proxy_options () | undefined .
473+ extract_proxy_options (List ) when is_list (List ) ->
474+ case {proplists :get_value (host , List , undefined ),
475+ proplists :get_value (port , List , 0 )} of
476+ {undefined , _ } -> undefined ;
477+ {_ , 0 } -> undefined ;
478+ {H , P } ->
479+ # proxy_options {
480+ host = H ,
481+ port = P ,
482+ username = proplists :get_value (username , List , undefined ),
483+ password = proplists :get_value (password , List , undefined )
484+ }
485+ end ;
486+ extract_proxy_options (Map ) ->
487+ case {maps :get (host , Map , undefined ), maps :get (port , Map , 0 )} of
488+ {undefined , _ } -> undefined ;
489+ {_ , 0 } -> undefined ;
490+ {H , P } ->
491+ # proxy_options {
492+ host = H ,
493+ port = P ,
494+ username = maps :get (username , Map , undefined ),
495+ password = maps :get (password , Map , undefined )
496+ }
497+ end .
400498
401499-spec extract_ssl_options_as_list (#{atom () => any ()}) -> proplists :proplist ().
402500extract_ssl_options_as_list (Map ) ->
@@ -522,15 +620,36 @@ append_extra_parameters(Request, QueryList) ->
522620 Params -> Params ++ QueryList
523621 end .
524622
525- get_ssl_options_if_any ( OAuthProvider ) ->
526- case OAuthProvider # oauth_provider . ssl_options of
623+ map_ssl_options_to_httpc_option ( SslOptions ) ->
624+ case SslOptions of
527625 undefined -> [];
528- Options -> [{ssl , Options }]
626+ Options -> [{ssl , Options }]
529627 end .
530- get_timeout_of_default (Timeout ) ->
628+
629+ map_timeout_to_httpc_option (Timeout ) ->
531630 case Timeout of
532631 undefined -> [{timeout , ? DEFAULT_HTTP_TIMEOUT }];
533- Timeout -> [{timeout , Timeout }]
632+ Timeout -> [{timeout , Timeout }]
633+ end .
634+
635+ map_proxy_to_httpc_option (ProxyOptions ) ->
636+ case ProxyOptions of
637+ undefined -> [];
638+ Proxy -> case {Proxy # proxy_options .host , Proxy # proxy_options .port } of
639+ {_ , 0 } -> [];
640+ {Host , Port } -> P = {{Host , Port },[]},
641+ [{proxy , P }]
642+ end
643+ end .
644+
645+ map_proxy_auth_to_httpc_option (ProxyOptions ) ->
646+ case ProxyOptions of
647+ undefined -> [];
648+ Proxy -> case {Proxy # proxy_options .username , Proxy # proxy_options .password } of
649+ {undefined , _ } -> [];
650+ {_ , undefined } -> [];
651+ {_ , _ } = Auth -> [{proxy_auth , Auth }]
652+ end
534653 end .
535654
536655is_json (? CONTENT_JSON ) -> true ;
@@ -543,10 +662,8 @@ is_json(_) -> false.
543662decode_body (_ , []) -> [];
544663decode_body (? CONTENT_JSON , Body ) ->
545664 case rabbit_json :try_decode (rabbit_data_coercion :to_binary (Body )) of
546- {ok , Value } ->
547- Value ;
548- {error , _ } = Error ->
549- Error
665+ {ok , Value } -> Value ;
666+ {error , _ } = Error -> Error
550667 end ;
551668decode_body (MimeType , Body ) ->
552669 Items = string :split (MimeType , " ;" ),
@@ -588,14 +705,14 @@ map_to_oauth_provider(PropList) when is_list(PropList) ->
588705 proplists :get_value (jwks_uri , PropList , undefined ),
589706 ssl_options =
590707 extract_ssl_options_as_list (maps :from_list (
591- proplists :get_value (https , PropList , [])))
708+ proplists :get_value (https , PropList , []))),
709+ proxy_options =
710+ extract_proxy_options (proplists :get_value (proxy , PropList , []))
592711 }.
593712map_to_access_token_response (Code , Reason , Headers , Body ) ->
594713 case decode_body (proplists :get_value (" content-type" , Headers , ? CONTENT_JSON ), Body ) of
595- {error , {error , InternalError }} ->
596- {error , InternalError };
597- {error , _ } = Error ->
598- Error ;
714+ {error , {error , InternalError }} -> {error , InternalError };
715+ {error , _ } = Error -> Error ;
599716 Value ->
600717 case Code of
601718 200 -> {ok , map_to_successful_access_token_response (Value )};
@@ -626,6 +743,18 @@ format_ssl_options(TlsOptions) ->
626743 proplists :get_value (cacertfile , TlsOptions ),
627744 CaCertsCount ])).
628745
746+ -spec format_proxy_options (proxy_options ()|undefined ) -> string ().
747+ format_proxy_options (undefined ) ->
748+ lists :flatten (io_lib :format (" {no proxy}" , []));
749+
750+ format_proxy_options (ProxyOptions ) ->
751+ lists :flatten (io_lib :format (" {host: ~p , port: ~p , username: ~p , " ++
752+ " password: ~p }" , [
753+ ProxyOptions # proxy_options .host ,
754+ ProxyOptions # proxy_options .port ,
755+ ProxyOptions # proxy_options .username ,
756+ ProxyOptions # proxy_options .password ])).
757+
629758format_oauth_provider_id (root ) -> " <from keyconfig>" ;
630759format_oauth_provider_id (Id ) -> binary_to_list (Id ).
631760
@@ -634,15 +763,16 @@ format_oauth_provider(OAuthProvider) ->
634763 lists :flatten (io_lib :format (" {id: ~p , issuer: ~p , discovery_endpoint: ~p , " ++
635764 " token_endpoint: ~p , " ++
636765 " authorization_endpoint: ~p , end_session_endpoint: ~p , " ++
637- " jwks_uri: ~p , ssl_options: ~p }" , [
766+ " jwks_uri: ~p , ssl_options: ~p , proxy_options: ~p }" , [
638767 format_oauth_provider_id (OAuthProvider # oauth_provider .id ),
639768 OAuthProvider # oauth_provider .issuer ,
640769 OAuthProvider # oauth_provider .discovery_endpoint ,
641770 OAuthProvider # oauth_provider .token_endpoint ,
642771 OAuthProvider # oauth_provider .authorization_endpoint ,
643772 OAuthProvider # oauth_provider .end_session_endpoint ,
644773 OAuthProvider # oauth_provider .jwks_uri ,
645- format_ssl_options (OAuthProvider # oauth_provider .ssl_options )])).
774+ format_ssl_options (OAuthProvider # oauth_provider .ssl_options ),
775+ format_proxy_options (OAuthProvider # oauth_provider .proxy_options )])).
646776
647777get_env (Par ) ->
648778 application :get_env (rabbitmq_auth_backend_oauth2 , Par , undefined ).
0 commit comments