@@ -18,8 +18,11 @@ namespace Microsoft.AspNet.Facebook.Authorization
1818 /// </summary>
1919 public class FacebookAuthorizeFilter : IAuthorizationFilter
2020 {
21- private static readonly Uri DefaultAuthorizationRedirectUrl = new Uri ( "https://www.facebook.com/" ) ;
22- private const string ExecuteMethodCannotBeCalledFormat = "The {0} execute method should not be called." ;
21+ private static readonly Uri FacebookUri = new Uri ( "https://www.facebook.com/" ) ;
22+ private static readonly Uri DefaultAuthorizationRedirectUrl = FacebookUri ;
23+ private static readonly Uri DefaultCannotCreateCookiesRedirectPath = FacebookUri ;
24+ // Facebook Missing Permissions, shortened version to not add excessively long query string parameters to the url.
25+ private const string MissingPermissionsQueryName = "__fb_mps" ;
2326
2427 private FacebookConfiguration _config ;
2528
@@ -77,6 +80,7 @@ public virtual void OnAuthorization(AuthorizationContext filterContext)
7780
7881 NameValueCollection parsedQueries = HttpUtility . ParseQueryString ( request . Url . Query ) ;
7982 HashSet < string > requiredPermissions = PermissionHelper . GetRequiredPermissions ( authorizeAttributes ) ;
83+
8084 bool handleError = ! String . IsNullOrEmpty ( parsedQueries [ "error" ] ) ;
8185
8286 // This must occur AFTER the handleError calculation because it modifies the parsed queries.
@@ -141,11 +145,29 @@ public virtual void OnAuthorization(AuthorizationContext filterContext)
141145 missingPermissions ,
142146 permissionContext . DeclinedPermissions ) ;
143147
148+ // Add a query string parameter that enables us to identify if we've already prompted for missing permissions
149+ // and therefore can detect cookies.
150+ AddCookieVerificationQuery ( parsedQueries ) ;
151+ // Rebuild the redirect Url so it contains the new query string parameter.
152+ redirectUrl = GetRedirectUrl ( request , parsedQueries ) ;
153+
144154 PromptMissingPermissions ( permissionContext , redirectUrl ) ;
145155 }
146156 }
147157 }
148158
159+ /// <summary>
160+ /// Adds a query string parameter to a <see cref="NameValueCollection"/> that enables a
161+ /// <see cref="FacebookAuthorizeFilter"/> to detect when cookies are unavailable and then trigger the
162+ /// <see cref="OnCannotCreateCookies"/> hook.
163+ /// </summary>
164+ /// <param name="queries">List of query parameters that are used to create a url.</param>
165+ protected static void AddCookieVerificationQuery ( NameValueCollection queries )
166+ {
167+ // Add a query string parameter that enables us to identify if we've already prompted for missing permissions
168+ queries . Add ( MissingPermissionsQueryName , String . Empty ) ;
169+ }
170+
149171 /// <summary>
150172 /// Called when authorization fails and need to create a redirect result.
151173 /// </summary>
@@ -176,6 +198,27 @@ protected ShowPromptResult ShowPrompt(PermissionContext context)
176198 return new ShowPromptResult ( navigationUrl ) ;
177199 }
178200
201+ /// <summary>
202+ /// Invoked during <see cref="OnAuthorization"/> after determining that cookies cannot be created. Default behavior
203+ /// redirects to a no cookies error page.
204+ /// </summary>
205+ /// <param name="context">Provides access to permission information associated with the user.</param>
206+ protected virtual void OnCannotCreateCookies ( PermissionContext context )
207+ {
208+ Uri redirectPath ;
209+
210+ if ( String . IsNullOrEmpty ( _config . CannotCreateCookieRedirectPath ) )
211+ {
212+ redirectPath = DefaultCannotCreateCookiesRedirectPath ;
213+ }
214+ else
215+ {
216+ redirectPath = GetCannotCreateCookiesUri ( ) ;
217+ }
218+
219+ context . Result = CreateRedirectResult ( redirectPath ) ;
220+ }
221+
179222 /// <summary>
180223 /// Invoked during <see cref="OnAuthorization"/> when a prompt requests permissions that were skipped or revoked.
181224 /// Set the <paramref name="context"/>'s Result property to modify login flow.
@@ -213,6 +256,14 @@ private Uri GetErroredAuthorizeUri(string originUrl, HashSet<string> requiredPer
213256 return authorizationUrlBuilder . Uri ;
214257 }
215258
259+ private Uri GetCannotCreateCookiesUri ( )
260+ {
261+ UriBuilder noCookiesUrlBuilder = new UriBuilder ( new Uri ( _config . AppUrl ) ) ;
262+ noCookiesUrlBuilder . Path += _config . CannotCreateCookieRedirectPath . Substring ( 1 ) ;
263+
264+ return noCookiesUrlBuilder . Uri ;
265+ }
266+
216267 private string GetRedirectUrl ( HttpRequestBase request )
217268 {
218269 NameValueCollection queryNameValuePair = HttpUtility . ParseQueryString ( request . Url . Query ) ;
@@ -273,21 +324,32 @@ private void PromptMissingPermissions(PermissionContext permissionContext, strin
273324
274325 permissionContext . RedirectUrl = redirectUrl ;
275326
276- // The DeniedPermissionPromptHook will only be invoked if we detect there are denied permissions.
277- // It is attempted instead of the permission hook to allow app creators to handle situations when a user
278- // skip's or revokes previously prompted permissions. Ex: redirect to a different page.
279- if ( deniedPermissions )
327+ // See if our persisted permissions cookie doesn't exist and if we've had missing permissions before.
328+ // Essentially this checks to see if we've tried to persist permissions before and were unsuccessful due to an
329+ // inability to create cookies.
330+ if ( ! PermissionHelper . RequestedPermissionsCookieExists ( filterContext . HttpContext . Request ) &&
331+ filterContext . HttpContext . Request . QueryString . Get ( MissingPermissionsQueryName ) != null )
280332 {
281- OnDeniedPermissionPrompt ( permissionContext ) ;
333+ OnCannotCreateCookies ( permissionContext ) ;
282334 }
283335 else
284336 {
285- OnPermissionPrompt ( permissionContext ) ;
286- }
337+ // The DeniedPermissionPromptHook will only be invoked if we detect there are denied permissions.
338+ // It is attempted instead of the permission hook to allow app creators to handle situations when a user
339+ // skip's or revokes previously prompted permissions. Ex: redirect to a different page.
340+ if ( deniedPermissions )
341+ {
342+ OnDeniedPermissionPrompt ( permissionContext ) ;
343+ }
344+ else
345+ {
346+ OnPermissionPrompt ( permissionContext ) ;
347+ }
287348
288- // We persist the requested permissions in a cookie to know if a permission was denied in any way.
289- // The persisted data allows us to detect skipping of permissions.
290- PermissionHelper . PersistRequestedPermissions ( filterContext , requiredPermissions ) ;
349+ // We persist the requested permissions in a cookie to know if a permission was denied in any way.
350+ // The persisted data allows us to detect skipping of permissions.
351+ PermissionHelper . PersistRequestedPermissions ( filterContext , requiredPermissions ) ;
352+ }
291353
292354 filterContext . Result = permissionContext . Result ;
293355 }
0 commit comments