Skip to content

Commit f6703ba

Browse files
committed
[Bug 18617] Implement OAuth2 dialog/library
1 parent 562324c commit f6703ba

1 file changed

Lines changed: 304 additions & 0 deletions

File tree

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
script "com.livecode.script-library.oauth2"
2+
/*
3+
Copyright (C) 2016 LiveCode Ltd.
4+
5+
This file is part of LiveCode.
6+
7+
LiveCode is free software; you can redistribute it and/or modify it under
8+
the terms of the GNU General Public License v3 as published by the Free
9+
Software Foundation.
10+
11+
LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY
12+
WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with LiveCode. If not see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
on revLoadLibrary
21+
if the target is not me then
22+
pass revLoadLibrary
23+
end if
24+
25+
insert the script of me into back
26+
27+
if the environment contains "development" then
28+
set the _ideoverride of me to true
29+
revSBAddAvailableLibrary the short name of me, "OAuth2"
30+
revSBAddDependencyForInclusion "scriptLibraries", "OAuth2", "externals", "mergJSON"
31+
end if
32+
end revLoadLibrary
33+
34+
on revUnloadLibrary
35+
if the target is not me then
36+
pass revUnloadLibrary
37+
end if
38+
39+
remove the script of me from back
40+
end revUnloadLibrary
41+
42+
/**
43+
44+
Present an authorization dialog for any web service that supports OAuth2 Authorization Code Flow
45+
46+
Requires mergJSON
47+
48+
*/
49+
50+
constant kRedirectURL = "http://localhost"
51+
local sPort = 49152
52+
53+
local sUniqueRefs
54+
55+
/**
56+
57+
Present an authorization dialog for any web service that supports OAuth2 Authorization Code Flow
58+
59+
Description:
60+
In order to handle the redirect the library accepts socket connections on localhost on a
61+
configurable port. The redirect uri configured when setting up your application with the
62+
web service should be `http://localhost:port` where `port` is the port that can be
63+
configured with `OAuth2Set "port", port`. The default port is 49152 and it is recommended
64+
to use the range 49152-65535.
65+
66+
> **Warning:** The client secret should be kept securely when distributing an application in
67+
> order to protect your application from malicious use. The recommended way to do this is to
68+
> include the client secret into a script in a password protected stack. If that is not possible
69+
> allow users to configure their own application with the web service and enter their own
70+
> client id and secret into a preference instead of distributing your client id and secret.
71+
72+
Example:
73+
constant kAuthURL = "https://slack.com/oauth/authorize"
74+
constant kTokenURL = "https://slack.com/api/oauth.access"
75+
constant kClientID = "XXXXXXXXX.XXXXXXXX"
76+
constant kClientSecret = "XXXXXXXXXXXXXXXXXXXXX"
77+
constant kScopes = "incoming-webhook"
78+
79+
OAuth2 kAuthURL, kTokenURL, kClientID, kClientSecret, kScopes
80+
if the result is not empty then
81+
answer error "Not authorized!"
82+
else
83+
local tAuth
84+
put it into tAuth
85+
local tMessage
86+
ask question "What do you want to send?"
87+
if it is empty then
88+
exit mouseUp
89+
end if
90+
91+
put it into tMessage["text"]
92+
put ArrayToJSON(tMessage) into tMessage
93+
94+
set the httpHeaders to "Content-type: application/json" & \
95+
return & "Authorization: token " & sAuth["access_token"]
96+
post tMessage to url tAuth["incoming_webhook"]["url"]
97+
end if
98+
99+
Parameters:
100+
pAuthURL (string): The URL to present for the authorization page. This can be obtained from the
101+
API documentation of the service being authorized.
102+
103+
pTokenURL (string): The URL to obtain the authorization token from once an authorization code is
104+
sent to the redirect uri. This can be obtained from the API documentation of the service being
105+
authorized.
106+
107+
pClientID (string): The application client ID obtained when setting up your application with
108+
the web service.
109+
110+
pClientSecret (string): The application client secret obtained when setting up your application with
111+
the web service.
112+
113+
pScopes (string): A comma delimited list of authorization scopes. Valid scopes will be found
114+
in the API documentation of the service being authorized.
115+
116+
pParams (array): An array of additional key -> value pairs of extra parameters to be sent to
117+
the authorization url. Some services implement additional options that require extra parameters.
118+
119+
The result:
120+
An error string if an error occured during authorization
121+
122+
It:
123+
An array containing the parsed JSON data returned by the token url
124+
125+
*/
126+
127+
command OAuth2 pAuthURL, pTokenURL, pClientID, pClientSecret, pScopes, pParams
128+
local tURL
129+
put pAuthURL & "?response_type=code" into tURL
130+
131+
put "&client_id=" & urlEncode(pClientID) after tURL
132+
133+
replace space with "%20" in pScopes
134+
put "&scope=" & urlEncode(pScopes) after tURL
135+
136+
local tUniqueRef
137+
put uuid() into tUniqueRef
138+
put "&state=" & tUniqueRef after tURL
139+
140+
local tKey
141+
repeat for each key tKey in pParams
142+
replace space with "%20" in pParams[tKey]
143+
put "&" & tKey & "=" & urlEncode(pParams[tKey]) after tURL
144+
end repeat
145+
146+
put "&redirect_uri=" & urlEncode(kRedirectURL & ":" & sPort & "/") after tURL
147+
148+
local tBrowserRect
149+
if the environment is not "mobile" then
150+
reset the templateStack
151+
create invisible stack tUniqueRef
152+
set the title of stack tUniqueRef to "Authenticate"
153+
154+
local tOldDefault
155+
put the defaultStack into tOldDefault
156+
set the defaultStack to tUniqueRef
157+
158+
set the width of stack tUniqueRef to 640
159+
set the height of stack tUniqueRef to 600
160+
put 0,0,640,600 into tBrowserRect
161+
else
162+
put the effective working screenrect into tBrowserRect
163+
end if
164+
165+
create widget tUniqueRef as "com.livecode.widget.browser"
166+
set the rect of widget tUniqueRef to tBrowserRect
167+
set the url of widget tUniqueRef to tURL
168+
169+
local tOldInterface
170+
put the defaultNetworkInterface into tOldInterface
171+
set the defaultNetworkInterface to "127.0.0.1"
172+
accept connections on port sPort with message "__NewConnection"
173+
set the defaultNetworkInterface to tOldInterface
174+
175+
put sPort into sUniqueRefs[tUniqueRef]
176+
177+
if the environment is not "mobile" then
178+
close stack tUniqueRef
179+
set the style of stack tUniqueRef to "modal"
180+
set the visible of stack tUniqueRef to true
181+
set the destroyStack of stack tUniqueRef to true
182+
183+
set the defaultStack to tOldDefault
184+
185+
sheet tUniqueRef
186+
else
187+
wait while exists(widget tUniqueRef) with messages
188+
end if
189+
190+
local tResult
191+
put the dialogData into tResult
192+
193+
if tResult["code"] is not empty then
194+
local tParams
195+
put "grant_type=authorization_code" into tParams
196+
put "&client_id=" & urlEncode(pClientID) after tParams
197+
put "&client_secret=" & urlEncode(pClientSecret) after tParams
198+
put "&code=" & urlEncode(tResult["code"]) after tParams
199+
put "&redirect_uri=" & urlEncode(kRedirectURL & ":" & sPort & "/") after tParams
200+
201+
local tResponse
202+
set the httpHeaders to "Accept: application/json"
203+
204+
post tParams to pTokenURL
205+
put JSONToArray(it) into tResponse
206+
207+
return tResponse for value
208+
else
209+
return tResult["error"] for error
210+
end if
211+
end OAuth2
212+
213+
/**
214+
215+
Set the value of a property
216+
217+
Example:
218+
OAuth2Set "port", 65535
219+
220+
Parameters:
221+
pProperty (enum): The property to set
222+
223+
- "port": The port to use for the redirect uri. Default value is 49152
224+
225+
pValue: The property value
226+
227+
*/
228+
229+
command OAuth2Set pProperty, pValue
230+
switch pProperty
231+
case "port"
232+
if pValue is not an integer or \
233+
pValue > 65535 or pValue < 1 then
234+
return "invalid port" for error
235+
end if
236+
237+
put pValue into sPort
238+
break
239+
end switch
240+
241+
return empty for error
242+
end OAuth2Set
243+
244+
command __NewConnection pSocketID
245+
read from socket pSocketID until crlf & crlf with message "__HandleRequest"
246+
end __NewConnection
247+
248+
command __HandleRequest pSocketID, pData
249+
close socket pSocketID
250+
251+
local tRequest
252+
put word 2 of pData into tRequest
253+
split tRequest by "?"
254+
split tRequest[2] by "&" and "="
255+
256+
local tUniqueRef
257+
put tRequest[2]["state"] into tUniqueRef
258+
259+
local tPort
260+
put sUniqueRefs[tUniqueRef] into tPort
261+
262+
if tPort is not empty then
263+
264+
set the dialogData to tRequest[2]
265+
266+
delete variable sUniqueRefs[tUniqueRef]
267+
268+
-- stop accepting only if there aren't any other dialogs
269+
-- using the same port
270+
local tStopAccepting = true
271+
local tOtherPort
272+
repeat for each element tOtherPort in sUniqueRefs
273+
if tOtherPort is tPort then
274+
put false into tStopAccepting
275+
exit repeat
276+
end if
277+
end repeat
278+
279+
if tStopAccepting then
280+
close socket tPort
281+
end if
282+
283+
if the environment is "mobile" then
284+
delete widget tUniqueRef
285+
else
286+
close stack tUniqueRef
287+
end if
288+
else
289+
290+
-- The OAuth2 spec requires the state parameter
291+
-- to be returned verbatim to prevent cross site
292+
-- request forgery so this is an error state on an
293+
-- unknown request... so we just burn them all
294+
295+
repeat for each key tUniqueRef in sUniqueRefs
296+
close socket sUniqueRefs[tUniqueRef]
297+
if the environment is "mobile" then
298+
delete widget tUniqueRef
299+
else
300+
close stack tUniqueRef
301+
end if
302+
end repeat
303+
end if
304+
end __HandleRequest

0 commit comments

Comments
 (0)