2222using Microsoft . Azure . WebJobs . Script . WebHost . Middleware ;
2323using Microsoft . Azure . WebJobs . Script . WebHost . Models ;
2424using Microsoft . Azure . WebJobs . Script . WebHost . Security ;
25- using Microsoft . Azure . WebJobs . Script . WebHost . Security . Authentication ;
2625using Microsoft . Azure . WebJobs . Script . Workers . Rpc ;
2726using Microsoft . Extensions . DependencyInjection ;
2827using Microsoft . Extensions . Logging ;
@@ -153,6 +152,74 @@ public async Task InvokeAdminLevelFunction_WithoutMasterKey_ReturnsUnauthorized(
153152 }
154153 }
155154
155+ [ Fact ]
156+ public async Task InvokeFunction_RequiresKeyOrNonPlatformToken ( )
157+ {
158+ // no key presented
159+ string uri = $ "api/httptrigger?name=Mathew";
160+ HttpRequestMessage request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
161+ HttpResponseMessage response = await _fixture . Host . HttpClient . SendAsync ( request ) ;
162+ Assert . Equal ( HttpStatusCode . Unauthorized , response . StatusCode ) ;
163+
164+ // required key supplied
165+ request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
166+ string key = await _fixture . Host . GetFunctionSecretAsync ( "httptrigger" ) ;
167+ request . Headers . Add ( AuthenticationLevelHandler . FunctionsKeyHeaderName , key ) ;
168+ response = await _fixture . Host . HttpClient . SendAsync ( request ) ;
169+ Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
170+
171+ // verify that even though a site token grants admin level access to
172+ // host APIs, it can't be used to invoke user functions
173+ using ( new TestScopedEnvironmentVariable ( EnvironmentSettingNames . WebSiteAuthEncryptionKey , TestHelpers . GenerateKeyHexString ( ) ) )
174+ {
175+ string swtToken = SimpleWebTokenHelper . CreateToken ( DateTime . UtcNow . AddMinutes ( 2 ) ) ;
176+
177+ // verify the token is valid by invoking an admin API
178+ request = new HttpRequestMessage ( HttpMethod . Get , "admin/host/status" ) ;
179+ request . Headers . Add ( ScriptConstants . SiteRestrictedTokenHeaderName , swtToken ) ;
180+ response = await _fixture . Host . HttpClient . SendAsync ( request ) ;
181+ Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
182+
183+ // verify it can't be used to invoke non-anonymous user functions
184+ request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
185+ request . Headers . Add ( ScriptConstants . SiteRestrictedTokenHeaderName , swtToken ) ;
186+ response = await _fixture . Host . HttpClient . SendAsync ( request ) ;
187+ Assert . Equal ( HttpStatusCode . Unauthorized , response . StatusCode ) ;
188+ }
189+
190+ // verify non-platform JWT token can be used to invoke non-anonymous user functions
191+ string jwtToken = _fixture . Host . GenerateAdminJwtToken ( ) ;
192+ request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
193+ request . Headers . Add ( ScriptConstants . SiteTokenHeaderName , jwtToken ) ;
194+ response = await _fixture . Host . HttpClient . SendAsync ( request ) ;
195+ Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
196+
197+ // verify platform JWT token can't be used to invoke non-anonymous user functions
198+ jwtToken = _fixture . Host . GenerateAdminJwtToken ( issuer : ScriptConstants . AppServiceCoreUri ) ;
199+ request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
200+ request . Headers . Add ( ScriptConstants . SiteTokenHeaderName , jwtToken ) ;
201+ response = await _fixture . Host . HttpClient . SendAsync ( request ) ;
202+ Assert . Equal ( HttpStatusCode . Unauthorized , response . StatusCode ) ;
203+ }
204+
205+ [ Fact ]
206+ public async Task InvokeFunction_AdminInvokeApi_Succeeds ( )
207+ {
208+ string functionName = "HttpTrigger" ;
209+
210+ // jwt token with site issuer
211+ var response = await AdminInvokeFunctionAdminToken ( functionName , true ) ;
212+ Assert . Equal ( HttpStatusCode . Accepted , response . StatusCode ) ;
213+
214+ // jwt token with platform issuer
215+ response = await AdminInvokeFunctionAdminToken ( functionName , true , issuer : ScriptConstants . AppServiceCoreUri ) ;
216+ Assert . Equal ( HttpStatusCode . Accepted , response . StatusCode ) ;
217+
218+ // swt token
219+ response = await AdminInvokeFunctionAdminToken ( functionName , false ) ;
220+ Assert . Equal ( HttpStatusCode . Accepted , response . StatusCode ) ;
221+ }
222+
156223 [ Fact ]
157224 public async Task ExtensionWebHook_Succeeds ( )
158225 {
@@ -667,7 +734,7 @@ await TestHelpers.RunWithTimeoutAsync(async () =>
667734 await VerifyOfflineResponse ( response ) ;
668735
669736 // verify the same thing when invoking via admin api
670- response = await AdminInvokeFunction ( functionName ) ;
737+ response = await AdminInvokeFunctionMasterKey ( functionName ) ;
671738 await VerifyOfflineResponse ( response ) ;
672739
673740 // bring host back online
@@ -682,7 +749,7 @@ await TestHelpers.RunWithTimeoutAsync(async () =>
682749 await SamplesTestHelpers . InvokeAndValidateHttpTrigger ( _fixture , functionName ) ;
683750
684751 // verify the same thing via admin api
685- response = await AdminInvokeFunction ( functionName ) ;
752+ response = await AdminInvokeFunctionMasterKey ( functionName ) ;
686753 Assert . Equal ( HttpStatusCode . Accepted , response . StatusCode ) ;
687754 }
688755
@@ -724,7 +791,7 @@ public async Task ListFunctions_Succeeds()
724791 var response = await _fixture . Host . HttpClient . SendAsync ( request ) ;
725792 var metadata = ( await response . Content . ReadAsAsync < IEnumerable < FunctionMetadataResponse > > ( ) ) . ToArray ( ) ;
726793
727- Assert . Equal ( 18 , metadata . Length ) ;
794+ Assert . Equal ( 19 , metadata . Length ) ;
728795 var function = metadata . Single ( p => p . Name == "HttpTrigger-CustomRoute" ) ;
729796 Assert . Equal ( "https://somewebsite.azurewebsites.net/api/csharp/products/{category:alpha?}/{id:int?}/{extra?}" , function . InvokeUrlTemplate . ToString ( ) ) ;
730797
@@ -892,7 +959,7 @@ public async Task HttpTrigger_CorrelationIDsAreLogged()
892959 Assert . Equal ( requestId , ( string ) jo [ "requestId" ] ) ;
893960 Assert . Equal ( 200 , jo [ "status" ] ) ;
894961 var duration = ( long ) jo [ "duration" ] ;
895- Assert . True ( duration > 0 ) ;
962+ Assert . True ( duration >= 0 ) ;
896963
897964 // determine the function invocation ID
898965 var scriptHostLogs = _fixture . Host . GetScriptHostLogMessages ( ) ;
@@ -956,8 +1023,18 @@ public async Task HttpTrigger_Poco_Get_Succeeds()
9561023 }
9571024 }
9581025
1026+ [ Fact ]
1027+ public async Task HttpTrigger_Anonymous_Get_Succeeds ( )
1028+ {
1029+ string uri = $ "api/httptrigger-anonymouslevel?name=Mathew";
1030+ HttpRequestMessage request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
1031+
1032+ HttpResponseMessage response = await _fixture . Host . HttpClient . SendAsync ( request ) ;
1033+ Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
1034+ }
1035+
9591036 // invoke a function via the admin invoke api
960- private async Task < HttpResponseMessage > AdminInvokeFunction ( string functionName , string input = null )
1037+ private async Task < HttpResponseMessage > AdminInvokeFunctionMasterKey ( string functionName , string input = null )
9611038 {
9621039 string masterKey = await _fixture . Host . GetMasterKeyAsync ( ) ;
9631040 string uri = $ "admin/functions/{ functionName } ?code={ masterKey } ";
@@ -971,6 +1048,32 @@ private async Task<HttpResponseMessage> AdminInvokeFunction(string functionName,
9711048 return await _fixture . Host . HttpClient . SendAsync ( request ) ;
9721049 }
9731050
1051+ private async Task < HttpResponseMessage > AdminInvokeFunctionAdminToken ( string functionName , bool jwt , string input = null , string issuer = null )
1052+ {
1053+
1054+ string uri = $ "admin/functions/{ functionName } ";
1055+ HttpRequestMessage request = new HttpRequestMessage ( HttpMethod . Post , uri ) ;
1056+ JObject jo = new JObject
1057+ {
1058+ { "input" , input }
1059+ } ;
1060+ request . Content = new StringContent ( jo . ToString ( ) ) ;
1061+ request . Content . Headers . ContentType = new MediaTypeHeaderValue ( "application/json" ) ;
1062+
1063+ if ( jwt )
1064+ {
1065+ string jwtToken = _fixture . Host . GenerateAdminJwtToken ( issuer : issuer ) ;
1066+ request . Headers . Add ( ScriptConstants . SiteTokenHeaderName , jwtToken ) ;
1067+ }
1068+ else
1069+ {
1070+ string swtToken = SimpleWebTokenHelper . CreateToken ( DateTime . UtcNow . AddMinutes ( 2 ) ) ;
1071+ request . Headers . Add ( ScriptConstants . SiteRestrictedTokenHeaderName , swtToken ) ;
1072+ }
1073+
1074+ return await _fixture . Host . HttpClient . SendAsync ( request ) ;
1075+ }
1076+
9741077 [ Fact ]
9751078 [ Trait ( TestTraits . Group , TestTraits . AdminIsolationTests ) ]
9761079 public async Task HttpTrigger_AdminLevel_AdminIsolationEnabled_Succeeds ( )
@@ -1310,6 +1413,7 @@ public override void ConfigureScriptHost(IWebJobsBuilder webJobsBuilder)
13101413 o . Functions = new [ ]
13111414 {
13121415 "HttpTrigger" ,
1416+ "HttpTrigger-AnonymousLevel" ,
13131417 "HttpTrigger-AdminLevel" ,
13141418 "HttpTrigger-Compat" ,
13151419 "HttpTrigger-CustomRoute" ,
0 commit comments