@@ -21,7 +21,6 @@ static void do_login(void);
2121static void do_logout (void );
2222static void do_redirect (const char * url );
2323static void do_search (char * query );
24- static void finish_login (void );
2524static void show_error (const char * title , const char * message , const char * error );
2625
2726
@@ -58,8 +57,6 @@ main(void)
5857 do_login ();
5958 else if (cgiGetSize ("LOGOUT" ))
6059 do_logout ();
61- else if (!cgiIsPOST () && (cgiGetSize ("code" ) || cgiGetSize ("error" )))
62- finish_login ();
6360 else
6461 do_dashboard ();
6562
@@ -88,27 +85,17 @@ do_dashboard(void)
8885
8986
9087/*
91- * 'do_login()' - Redirect to the OAuth server's authorization endpoint .
88+ * 'do_login()' - Start or continue the OAuth device authorization flow .
9289 */
9390
9491static void
9592do_login (void )
9693{
97- const char * oauth_uri = getenv ("CUPS_OAUTH_SERVER" ),
98- // OAuth authorization server URL
99- * referer = getenv ("HTTP_REFERER" ),
100- // Referer: header
101- * server_name = getenv ("SERVER_NAME" ),
102- // SERVER_NAME value
103- * server_port = getenv ("SERVER_PORT" ),
104- // SERVER_PORT value
105- * state = NULL ; // State string
106- char * client_id = NULL , // Client ID value
107- * code_verifier = NULL , // Code verifier string
108- * nonce = NULL , // Nonce string
109- redirect_uri [1024 ], // redirect_uri value
110- * url = NULL ; // Authorization URL
111- cups_json_t * metadata = NULL ; // OAuth metadata
94+ const char * oauth_uri = getenv ("CUPS_OAUTH_SERVER" );
95+ // OAuth authorization server URL
96+ cups_json_t * metadata = NULL ; // OAuth metadata
97+ const char * devgrant_cookie ; // CUPS_DEVGRANT cookie value, if any
98+ cups_json_t * devgrant = NULL ; // Device grant JSON, if any
11299
113100
114101 fputs ("DEBUG2: do_login()\n" , stderr );
@@ -120,58 +107,102 @@ do_login(void)
120107 goto done ;
121108 }
122109
123- fprintf (stderr , "DEBUG2: do_login: oauth_uri=\"%s\"\n" , oauth_uri );
110+ // See if we have a pending device grant...
111+ if ((devgrant_cookie = cgiGetCookie ("CUPS_DEVGRANT" )) != NULL && devgrant_cookie [0 ])
112+ devgrant = cupsJSONImportString (devgrant_cookie );
124113
125- // Get the redirect URL...
126- if (!strcmp (server_name , "localhost" ))
127- snprintf (redirect_uri , sizeof (redirect_uri ), "http://127.0.0.1:%s/" , server_port );
128- else
129- snprintf (redirect_uri , sizeof (redirect_uri ), "%s://%s:%s/" , getenv ("HTTPS" ) ? "https" : "http" , server_name , server_port );
114+ if (devgrant )
115+ {
116+ const char * device_code ; // Device code
130117
131- fprintf (stderr , "DEBUG2: do_login: redirect_uri=\"%s\"\n" , redirect_uri );
118+ if ((device_code = cupsJSONGetString (cupsJSONFind (devgrant , CUPS_ODEVGRANT_DEVICE_CODE ))) != NULL )
119+ {
120+ char * bearer ; // Access token
121+ time_t access_expires ; // Expiration date
122+
123+ bearer = cupsOAuthGetTokens (oauth_uri , metadata , NULL , device_code , CUPS_OGRANT_DEVICE_CODE , NULL , & access_expires );
124+
125+ if (bearer )
126+ {
127+ // Got a token - save it, clear the grant cookie, and go home...
128+ cgiSetCookie ("CUPS_DEVGRANT" , "" , NULL , NULL , time (NULL ) - 1 , 0 );
129+ cgiSetCookie ("CUPS_BEARER" , bearer , NULL , NULL , access_expires , getenv ("HTTPS" ) ? 1 : 0 );
130+
131+ free (bearer );
132+
133+ do_redirect ("/" );
134+ goto done ;
135+ }
136+ else if (access_expires == 0 )
137+ {
138+ // Hard failure - discard this grant, fall through to request a new one...
139+ cupsJSONDelete (devgrant );
140+ devgrant = NULL ;
141+ }
142+ // else: still pending ("authorization_pending"/"slow_down") - fall
143+ // through to show the same grant again...
144+ }
145+ else
146+ {
147+ // Malformed grant JSON - discard and fall through...
148+ cupsJSONDelete (devgrant );
149+ devgrant = NULL ;
150+ }
151+ }
132152
133- // Get the client ID...
134- if ((client_id = cupsOAuthCopyClientId (oauth_uri , redirect_uri )) == NULL )
153+ if (!devgrant )
135154 {
136- // Nothing saved, try to dynamically register one...
137- if ((client_id = cupsOAuthGetClientId (oauth_uri , metadata , redirect_uri , /*logo_uri*/ NULL , /*tos_uri*/ NULL )) == NULL )
155+ char * temp ; // JSON string
156+
157+ if (!cgiIsPOST ())
138158 {
139- // Nope, show an error...
140- show_error (cgiText (_ ("OAuth Login" )), cgiText (_ ("Unable to get authorization URL" )), cgiText (_ ("No client ID configured for this server." )));
159+ // No pending grant, and this wasn't a POST (e.g. a stray GET to
160+ // "/?LOGIN=...") - don't silently start a new authorization request
161+ // against the OAuth server, just show the dashboard...
162+ do_dashboard ();
141163 goto done ;
142164 }
143- }
144165
145- fprintf (stderr , "DEBUG2: do_login: client_id=\"%s\"\n" , client_id );
166+ // Request a new device grant...
167+ if ((devgrant = cupsOAuthGetDeviceGrant (oauth_uri , metadata , NULL , getenv ("CUPS_OAUTH_SCOPES" ))) == NULL )
168+ {
169+ show_error (cgiText (_ ("OAuth Login" )), cgiText (_ ("Unable to get authorization URL" )), cupsGetErrorString ());
170+ goto done ;
171+ }
146172
147- // Make state and code verification strings...
148- code_verifier = cupsOAuthMakeBase64Random (128 );
149- nonce = cupsOAuthMakeBase64Random (16 );
150- state = cgiGetCookie (CUPS_SID );
173+ if ((temp = cupsJSONExportString (devgrant )) != NULL )
174+ {
175+ cgiSetCookie ("CUPS_DEVGRANT" , temp , NULL , NULL , time (NULL ) + (time_t )cupsJSONGetNumber (cupsJSONFind (devgrant , CUPS_ODEVGRANT_EXPIRES_IN )), 0 );
176+ free (temp );
177+ }
178+ }
151179
152- // Get the authorization URL
153- if ((url = cupsOAuthMakeAuthorizationURL (oauth_uri , metadata , /*resource_uri*/ NULL , getenv ("CUPS_OAUTH_SCOPES" ), client_id , code_verifier , nonce , redirect_uri , state )) == NULL )
180+ // Show the authorization page...
154181 {
155- show_error (cgiText (_ ("OAuth Login" )), cgiText (_ ("Unable to get authorization URL" )), cupsGetErrorString ());
156- goto done ;
157- }
182+ const char * user_code = cupsJSONGetString (cupsJSONFind (devgrant , CUPS_ODEVGRANT_USER_CODE ));
183+ const char * verification_uri = cupsJSONGetString (cupsJSONFind (devgrant , CUPS_ODEVGRANT_VERIFICATION_URI ));
184+ double interval = cupsJSONGetNumber (cupsJSONFind (devgrant , CUPS_ODEVGRANT_INTERVAL ));
185+ char refresh [32 ]; // Refresh value
186+
187+ snprintf (refresh , sizeof (refresh ), "%d;URL=/?LOGIN=Login" , interval > 0.0 ? (int )interval : 5 );
188+ cgiSetVariable ("REFRESH_PAGE" , refresh );
158189
159- // Redirect...
160- cgiSetCookie ("CUPS_OAUTH_STATE" , state , /*path*/ NULL , /*domain*/ NULL , time (NULL ) + 300 , /*secure*/ 0 );
190+ cgiSetVariable ("USER_CODE" , user_code ? user_code : "" );
161191
162- if (referer )
163- cgiSetCookie ("CUPS_REFERRER" , referer , /*path*/ NULL , /*domain*/ NULL , time (NULL ) + 300 , /*secure*/ 0 );
192+ if (verification_uri && (!strncmp (verification_uri , "http://" , 7 ) || !strncmp (verification_uri , "https://" , 8 )))
193+ cgiSetVariable ("VERIFICATION_URI" , verification_uri );
194+ else
195+ cgiSetVariable ("VERIFICATION_URI" , "" );
164196
165- do_redirect (url );
197+ cgiStartHTML (cgiText (_ ("Authorize Access" )));
198+ cgiCopyTemplateLang ("oauth-login.tmpl" );
199+ cgiEndHTML ();
200+ }
166201
167202 done :
168203
169- // Free memory...
170- free (client_id );
171- free (code_verifier );
204+ cupsJSONDelete (devgrant );
172205 cupsJSONDelete (metadata );
173- free (nonce );
174- free (url );
175206}
176207
177208
@@ -182,10 +213,19 @@ do_login(void)
182213static void
183214do_logout (void )
184215{
216+ const char * oauth_uri = getenv ("CUPS_OAUTH_SERVER" );
217+ // OAuth authorization server URL
218+
185219 // Clear the CUPS_BEARER cookie...
186- cgiSetCookie ("CUPS_BEARER" , "" , /*path*/ NULL , /*domain*/ NULL , time (NULL ) - 1 , /*secure*/ 0 );
220+ cgiSetCookie ("CUPS_BEARER" , "" , NULL , NULL , time (NULL ) - 1 , 0 );
187221
188- // Redirect back to the referrer...
222+ // Clear any pending device grant cookie...
223+ cgiSetCookie ("CUPS_DEVGRANT" , "" , NULL , NULL , time (NULL ) - 1 , 0 );
224+
225+ // Clear the stored OAuth tokens server-side as well...
226+ cupsOAuthClearTokens (oauth_uri , NULL );
227+
228+ // Redirect back to the dashboard...
189229 do_redirect ("/" );
190230}
191231
@@ -231,107 +271,6 @@ do_search(char *query) /* I - Search string */
231271}
232272
233273
234- //
235- // 'finish_login()' - Finish OAuth login and then redirect back to the original page.
236- //
237-
238- static void
239- finish_login (void )
240- {
241- const char * oauth_uri = getenv ("CUPS_OAUTH_SERVER" ),
242- // OAuth authorization server URL
243- * referer = getenv ("CUPS_REFERER" ),
244- // Referring URL
245- * server_name = getenv ("SERVER_NAME" ),
246- // SERVER_NAME value
247- * server_port = getenv ("SERVER_PORT" );
248- // SERVER_PORT value
249- char * bearer = NULL , // Bearer token
250- * client_id = NULL , // Client ID value
251- * error , // Error string
252- redirect_uri [1024 ]; // redirect_uri value
253- const char * code ; // Authorization code
254- cups_json_t * metadata = NULL ; // OAuth metadata
255- time_t access_expires ; // When the bearer token expires
256- char scheme [32 ], // Referer scheme
257- userpass [256 ], // Referer username:password
258- host [256 ], // Referer host
259- resource [1024 ]; // Referer resource
260- int port ; // Referer port
261-
262-
263- // Show any error from authorization...
264- if ((error = cgiGetVariable ("error_description" )) == NULL )
265- error = cgiGetVariable ("error" );
266-
267- if (error )
268- {
269- show_error (cgiText (_ ("OAuth Login" )), cgiText (_ ("Unable to authorize access" )), error );
270- return ;
271- }
272-
273- // Get the metadata...
274- if ((metadata = cupsOAuthGetMetadata (oauth_uri )) == NULL )
275- {
276- show_error (cgiText (_ ("OAuth Login" )), cgiText (_ ("Unable to get authorization server information" )), cupsGetErrorString ());
277- goto done ;
278- }
279-
280- // Get the redirect URL...
281- if (!server_name || !strcmp (server_name , "localhost" ))
282- snprintf (redirect_uri , sizeof (redirect_uri ), "http://127.0.0.1%s%s/" , server_port ? ":" : "" , server_port ? server_port : "" );
283- else
284- snprintf (redirect_uri , sizeof (redirect_uri ), "%s://%s%s%s/" , getenv ("HTTPS" ) ? "https" : "http" , server_name , server_port ? ":" : "" , server_port ? server_port : "" );
285-
286- fprintf (stderr , "DEBUG2: finish_login: redirect_uri=\"%s\"\n" , redirect_uri );
287-
288- // Get the client ID...
289- if ((client_id = cupsOAuthCopyClientId (oauth_uri , redirect_uri )) == NULL )
290- {
291- // Nope, show an error...
292- show_error (cgiText (_ ("OAuth Login" )), cgiText (_ ("Unable to authorize access" )), cgiText (_ ("No client ID configured for this server." )));
293- goto done ;
294- }
295-
296- fprintf (stderr , "DEBUG2: finish_login: client_id=\"%s\"\n" , client_id );
297-
298- // Get the code string...
299- code = cgiGetVariable ("code" );
300-
301- // Get the access token...
302- if ((bearer = cupsOAuthGetTokens (oauth_uri , metadata , /*resource_uri*/ NULL , code , CUPS_OGRANT_AUTHORIZATION_CODE , redirect_uri , & access_expires )) == NULL )
303- {
304- show_error (cgiText (_ ("OAuth Login" )), cgiText (_ ("Unable to authorize access" )), cupsGetErrorString ());
305- goto done ;
306- }
307-
308- fprintf (stderr , "DEBUG2: finish_login: access_token=\"%s\", access_expires=%ld\n" , bearer , (long )access_expires );
309-
310- // Save it as a cookie...
311- cgiSetCookie ("CUPS_BEARER" , bearer , /*path*/ NULL , /*domain*/ NULL , access_expires , /*secure*/ 0 );
312-
313- // Redirect...
314- if (referer && server_name && server_port )
315- {
316- // Validate refererring URL value - must be http: or https:, use the server
317- // name or localhost addresses, and use the same port...
318- if (httpSeparateURI (HTTP_URI_CODING_ALL , referer , scheme , sizeof (scheme ), userpass , sizeof (userpass ), host , sizeof (host ), & port , resource , sizeof (resource )) < HTTP_URI_STATUS_OK || (strcmp (scheme , "http" ) && strcmp (scheme , "https" )) || (strcasecmp (host , server_name ) && strcmp (host , "127.0.0.1" ) && strcmp (host , "[::1]" )) || port != atoi (server_port ))
319- referer = NULL ;
320- }
321-
322- do_redirect (referer ? referer : "/" );
323-
324- fputs ("DEBUG2: finish_login: After redirect.\n" , stderr );
325-
326- done :
327-
328- // Free memory...
329- free (bearer );
330- free (client_id );
331- cupsJSONDelete (metadata );
332- }
333-
334-
335274//
336275// 'show_error()' - Show an error message.
337276//
0 commit comments