1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Net ;
5+ using System . Net . Http ;
6+ using System . Runtime . CompilerServices ;
7+ using System . Web . Http . Controllers ;
8+ using System . Web . Http . Filters ;
9+ using Microsoft . Azure . WebJobs . Script ;
10+
11+ namespace WebJobs . Script . WebHost . Filters
12+ {
13+ public class AuthorizationLevelAttribute : AuthorizationFilterAttribute
14+ {
15+ public const string MasterKeyHeaderName = "x-functions-key" ;
16+
17+ public AuthorizationLevelAttribute ( AuthorizationLevel level )
18+ {
19+ Level = level ;
20+ }
21+
22+ public AuthorizationLevel Level { get ; private set ; }
23+
24+ public override void OnAuthorization ( HttpActionContext actionContext )
25+ {
26+ SecretManager secretManager = ( SecretManager ) actionContext . ControllerContext . Configuration . DependencyResolver . GetService ( typeof ( SecretManager ) ) ;
27+
28+ if ( ! IsAuthorized ( actionContext . Request , Level , secretManager ) )
29+ {
30+ actionContext . Response = new HttpResponseMessage ( HttpStatusCode . Unauthorized ) ;
31+ }
32+ }
33+
34+ public static bool IsAuthorized ( HttpRequestMessage request , AuthorizationLevel level , SecretManager secretManager , string functionName = null )
35+ {
36+ if ( level == AuthorizationLevel . Anonymous )
37+ {
38+ return true ;
39+ }
40+
41+ AuthorizationLevel requestLevel = GetAuthorizationLevel ( request , secretManager , functionName ) ;
42+ return requestLevel >= level ;
43+ }
44+
45+ private static AuthorizationLevel GetAuthorizationLevel ( HttpRequestMessage request , SecretManager secretManager , string functionName = null )
46+ {
47+ // TODO: Add support for validating "EasyAuth" headers
48+
49+ // first see if a key value is specified via headers or query string
50+ IEnumerable < string > values ;
51+ string keyValue = null ;
52+ if ( request . Headers . TryGetValues ( MasterKeyHeaderName , out values ) )
53+ {
54+ keyValue = values . FirstOrDefault ( ) ;
55+ }
56+ else
57+ {
58+ var queryParameters = request . GetQueryNameValuePairs ( ) . ToDictionary ( p => p . Key , p => p . Value , StringComparer . OrdinalIgnoreCase ) ;
59+ queryParameters . TryGetValue ( "key" , out keyValue ) ;
60+ }
61+
62+ if ( ! string . IsNullOrEmpty ( keyValue ) )
63+ {
64+ // see if the key specified is the master key
65+ HostSecrets hostSecrets = secretManager . GetHostSecrets ( ) ;
66+ if ( ! string . IsNullOrEmpty ( hostSecrets . MasterKey ) &&
67+ SecretEqual ( keyValue , hostSecrets . MasterKey ) )
68+ {
69+ return AuthorizationLevel . Admin ;
70+ }
71+
72+ // see if the key specified matches the host function key
73+ if ( ! string . IsNullOrEmpty ( hostSecrets . FunctionKey ) &&
74+ SecretEqual ( keyValue , hostSecrets . FunctionKey ) )
75+ {
76+ return AuthorizationLevel . Function ;
77+ }
78+
79+ // if there is a function specific key specified try to match against that
80+ if ( functionName != null )
81+ {
82+ FunctionSecrets functionSecrets = secretManager . GetFunctionSecrets ( functionName ) ;
83+ if ( functionSecrets != null &&
84+ ! string . IsNullOrEmpty ( functionSecrets . Key ) &&
85+ SecretEqual ( keyValue , functionSecrets . Key ) )
86+ {
87+ return AuthorizationLevel . Function ;
88+ }
89+ }
90+ }
91+
92+ return AuthorizationLevel . Anonymous ;
93+ }
94+
95+ /// <summary>
96+ /// Provides a time consistent comparison of two secrets in the form of two strings.
97+ /// This prevents security attacks that attempt to determine key values based on response
98+ /// times.
99+ /// </summary>
100+ /// <param name="inputA">The first secret to compare.</param>
101+ /// <param name="inputB">The second secret to compare.</param>
102+ /// <returns>Returns <c>true</c> if the two secrets are equal, <c>false</c> otherwise.</returns>
103+ [ MethodImpl ( MethodImplOptions . NoOptimization ) ]
104+ private static bool SecretEqual ( string inputA , string inputB )
105+ {
106+ if ( ReferenceEquals ( inputA , inputB ) )
107+ {
108+ return true ;
109+ }
110+
111+ if ( inputA == null || inputB == null || inputA . Length != inputB . Length )
112+ {
113+ return false ;
114+ }
115+
116+ bool areSame = true ;
117+ for ( int i = 0 ; i < inputA . Length ; i ++ )
118+ {
119+ areSame &= inputA [ i ] == inputB [ i ] ;
120+ }
121+
122+ return areSame ;
123+ }
124+ }
125+ }
0 commit comments