1:- module(bitrix24_auth, [ 2 load_context/3, 3 check_context/3, 4 refresh_context/3, 5 context_expiring_soon/1 6 ]). 7 8:- use_module(library(http/http_client)). 9:- use_module(bitrix24_utils). 10 11load_context(Provider, ContextRef, Context) :- 12 call(Provider:load_context(ContextRef, Context)). 13 14check_context(Provider, ContextRef, Context) :- 15 load_context(Provider, ContextRef, Context0), 16 ( context_expiring_soon(Context0) 17 -> refresh_context(Provider, ContextRef, Context) 18 ; Context = Context0 19 ). 20 21context_expiring_soon(Context) :- 22 get_dict(expires_at, Context, ExpiresAt0), 23 normalize_integer(ExpiresAt0, ExpiresAt), 24 get_time(Now), 25 Diff is ExpiresAt - Now, 26 Diff < 100. 27 28refresh_context(Provider, ContextRef, Context) :- 29 load_context(Provider, ContextRef, Context0), 30 call(Provider:get_config(client_id, ClientID)), 31 call(Provider:get_config(client_secret, ClientSecret)), 32 oauth_token_url(Provider, Url), 33 RefreshToken = Context0.refresh_token, 34 Attempts = 3, 35 refresh_attempt(Attempts, Url, ClientID, ClientSecret, RefreshToken, Context0, Context), 36 call(Provider:save_context(ContextRef, Context)). 37 38oauth_token_url(Provider, Url) :- 39 ( call(Provider:get_config(oauth_token_url, Url0)) 40 -> Url = Url0 41 ; Url = 'https://oauth.bitrix.info/oauth/token/' 42 ). 43 44refresh_attempt(AttemptsLeft, Url, ClientID, ClientSecret, RefreshToken, Context0, Context) :- 45 Body = form([ grant_type=refresh_token, 46 client_id=ClientID, 47 client_secret=ClientSecret, 48 refresh_token=RefreshToken 49 ]), 50 catch( 51 http_post(Url, Body, Reply, [status_code(StatusCode)]), 52 Error, 53 true 54 ), 55 ( var(Error) 56 -> handle_refresh_reply(StatusCode, Reply, Context0, Context, AttemptsLeft, 57 Url, ClientID, ClientSecret, RefreshToken) 58 ; handle_refresh_transport_error(Error, AttemptsLeft, Url, ClientID, ClientSecret, 59 RefreshToken, Context0, Context) 60 ). 61 62handle_refresh_transport_error(Error, AttemptsLeft, Url, ClientID, ClientSecret, RefreshToken, 63 Context0, Context) :- 64 ( AttemptsLeft > 1 65 -> sleep(1), 66 NextAttempts is AttemptsLeft - 1, 67 refresh_attempt(NextAttempts, Url, ClientID, ClientSecret, RefreshToken, Context0, Context) 68 ; throw(error(bitrix24_auth_transport_error(Error), _)) 69 ). 70 71handle_refresh_reply(200, Reply, Context0, Context, _AttemptsLeft, _Url, _ClientID, 72 _ClientSecret, _RefreshToken) :- 73 !, 74 bitrix24_utils:decode_response(Reply, Response), 75 merge_refresh_response(Context0, Response, Context). 76handle_refresh_reply(StatusCode, Reply, Context0, Context, AttemptsLeft, Url, ClientID, 77 ClientSecret, RefreshToken) :- 78 bitrix24_utils:decode_response(Reply, Response), 79 ( AttemptsLeft > 1 80 -> sleep(1), 81 NextAttempts is AttemptsLeft - 1, 82 refresh_attempt(NextAttempts, Url, ClientID, ClientSecret, RefreshToken, Context0, Context) 83 ; throw(error(bitrix24_auth_failed(StatusCode, Response), _)) 84 ). 85 86merge_refresh_response(Context0, Response, Context) :- 87 response_field(Response, access_token, AccessToken), 88 response_field(Response, refresh_token, RefreshToken), 89 response_field(Response, expires_in, ExpiresIn0), 90 normalize_integer(ExpiresIn0, ExpiresIn), 91 get_time(Now), 92 ExpiresAt is floor(Now) + ExpiresIn - 60, 93 Context1 = Context0.put(_{ 94 access_token: AccessToken, 95 refresh_token: RefreshToken, 96 expires_at: ExpiresAt 97 }), 98 maybe_put_field(Context1, Response, client_endpoint, Context2), 99 maybe_put_field(Context2, Response, server_endpoint, Context3), 100 maybe_put_field(Context3, Response, domain, Context4), 101 maybe_put_field(Context4, Response, member_id, Context5), 102 maybe_put_field(Context5, Response, user_id, Context). 103 104maybe_put_field(Context0, Response, Field, Context) :- 105 ( response_optional_field(Response, Field, Value) 106 -> Context = Context0.put(Field, Value) 107 ; Context = Context0 108 ). 109 110response_field(Response, Field, Value) :- 111 is_dict(Response), 112 !, 113 get_dict(Field, Response, Value). 114response_field(Response, Field, Value) :- 115 memberchk(Field=Value, Response). 116 117response_optional_field(Response, Field, Value) :- 118 is_dict(Response), 119 !, 120 get_dict(Field, Response, Value). 121response_optional_field(Response, Field, Value) :- 122 memberchk(Field=Value, Response). 123 124normalize_integer(Value, Integer) :- 125 integer(Value), 126 !, 127 Integer = Value. 128normalize_integer(Value, Integer) :- 129 number(Value), 130 !, 131 Integer is floor(Value). 132normalize_integer(Value, Integer) :- 133 string(Value), 134 !, 135 number_string(Integer, Value). 136normalize_integer(Value, Integer) :- 137 atom(Value), 138 atom_number(Value, Integer)