diff --git a/AppAuth.xcodeproj/project.pbxproj b/AppAuth.xcodeproj/project.pbxproj index a6711dbc3..7a3f6cdbc 100644 --- a/AppAuth.xcodeproj/project.pbxproj +++ b/AppAuth.xcodeproj/project.pbxproj @@ -481,6 +481,36 @@ A6DEABAA2018E5B50022AC32 /* OIDExternalUserAgentIOS.m in Sources */ = {isa = PBXBuildFile; fileRef = A6DEABA82018E5B50022AC32 /* OIDExternalUserAgentIOS.m */; }; A6DEABAB2018E5C50022AC32 /* OIDExternalUserAgentIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = A6DEABA92018E5B50022AC32 /* OIDExternalUserAgentIOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; A6DEABAF2018E5D80022AC32 /* OIDExternalUserAgentIOS.m in Sources */ = {isa = PBXBuildFile; fileRef = A6DEABA82018E5B50022AC32 /* OIDExternalUserAgentIOS.m */; }; + A6CEB11A2007E49C009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11B2007E49D009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11C2007E49E009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11D2007E49F009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11E2007E4A1009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6CEB11F2007E4A2009D492A /* OIDEndSessionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */; }; + A6DEAB832017A7030022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB842017A7040022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB852017A7050022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB862017A7060022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB872017A70B0022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB882017A70B0022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB892017A70C0022AC32 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; + A6DEAB8A2017A7140022AC32 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + A6DEAB8B2017A7160022AC32 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + A6DEAB8C2017A7160022AC32 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + A6DEAB8D2017A7170022AC32 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + A6DEABB02018ECE80022AC32 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB12018ECE80022AC32 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB22018ECE90022AC32 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB32018ECE90022AC32 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB42018ECF20022AC32 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB52018ECF30022AC32 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB62018ECF30022AC32 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A6DEABB72018ECF40022AC32 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CF37C06E1F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + CF37C06F1F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + CF37C0701F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + CF37C0711F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */; }; + CF6431F41F228A980075B6B5 /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -686,6 +716,12 @@ A6DEAB9A2018E4A20022AC32 /* OIDExternalUserAgentRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDExternalUserAgentRequest.h; sourceTree = ""; }; A6DEABA82018E5B50022AC32 /* OIDExternalUserAgentIOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OIDExternalUserAgentIOS.m; path = iOS/OIDExternalUserAgentIOS.m; sourceTree = ""; }; A6DEABA92018E5B50022AC32 /* OIDExternalUserAgentIOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OIDExternalUserAgentIOS.h; path = iOS/OIDExternalUserAgentIOS.h; sourceTree = ""; }; + A6CEB1172007E384009D492A /* OIDEndSessionRequestTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDEndSessionRequestTests.h; sourceTree = ""; }; + A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDEndSessionRequestTests.m; sourceTree = ""; }; + CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDEndSessionRequest.h; sourceTree = ""; }; + CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDEndSessionRequest.m; sourceTree = ""; }; + CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDEndSessionResponse.h; sourceTree = ""; }; + CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDEndSessionResponse.m; sourceTree = ""; }; F6F60FB01D2BFEFE00325CB3 /* OIDAuthState+IOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "OIDAuthState+IOS.m"; path = "iOS/OIDAuthState+IOS.m"; sourceTree = ""; }; F6F60FB11D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "OIDAuthorizationService+IOS.m"; path = "iOS/OIDAuthorizationService+IOS.m"; sourceTree = ""; }; F6F60FB31D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "OIDAuthorizationService+IOS.h"; path = "iOS/OIDAuthorizationService+IOS.h"; sourceTree = ""; }; @@ -900,6 +936,10 @@ A6DEAB982018E4A20022AC32 /* OIDExternalUserAgent.h */, 341741C31C5D8243000EF209 /* OIDFieldMapping.h */, 341741C41C5D8243000EF209 /* OIDFieldMapping.m */, + CF6431F21F228A980075B6B5 /* OIDEndSessionResponse.h */, + CF6431F31F228A980075B6B5 /* OIDEndSessionResponse.m */, + CF37C06B1F1FC21A00662E41 /* OIDEndSessionRequest.h */, + CF37C06C1F1FC21A00662E41 /* OIDEndSessionRequest.m */, 60140F7E1DE4335200DA0DC3 /* OIDRegistrationResponse.h */, 60140F7F1DE4344200DA0DC3 /* OIDRegistrationResponse.m */, 60140F7D1DE42E3000DA0DC3 /* OIDRegistrationRequest.h */, @@ -945,6 +985,8 @@ 60140F811DE43B4D00DA0DC3 /* OIDRegistrationRequestTests.h */, 341742061C5D82D3000EF209 /* OIDGrantTypesTests.m */, 341742071C5D82D3000EF209 /* OIDResponseTypesTests.m */, + A6CEB1172007E384009D492A /* OIDEndSessionRequestTests.h */, + A6CEB1182007E384009D492A /* OIDEndSessionRequestTests.m */, 341742081C5D82D3000EF209 /* OIDScopesTests.m */, 341742091C5D82D3000EF209 /* OIDServiceConfigurationTests.h */, 3417420A1C5D82D3000EF209 /* OIDServiceConfigurationTests.m */, @@ -1081,6 +1123,7 @@ 343AAAF71E83499000F9D36E /* OIDTokenRequest.h in Headers */, 343AAAF41E83499000F9D36E /* OIDScopeUtilities.h in Headers */, 343AAAF81E83499000F9D36E /* OIDTokenResponse.h in Headers */, + A6DEABB42018ECF20022AC32 /* OIDEndSessionResponse.h in Headers */, 343AAAF61E83499000F9D36E /* OIDServiceDiscovery.h in Headers */, 343AAAF11E83499000F9D36E /* OIDGrantTypes.h in Headers */, 55A094CF20DFBB10000045D1 /* OIDURLSessionProvider.h in Headers */, @@ -1092,6 +1135,7 @@ 343AAAE91E83499000F9D36E /* OIDAuthStateErrorDelegate.h in Headers */, 343AAAE51E83499000F9D36E /* OIDAuthorizationService.h in Headers */, 343AAAF01E83499000F9D36E /* OIDRegistrationRequest.h in Headers */, + A6DEABB02018ECE80022AC32 /* OIDEndSessionRequest.h in Headers */, 343AAAE71E83499000F9D36E /* OIDAuthState.h in Headers */, 343AAAED1E83499000F9D36E /* OIDErrorUtilities.h in Headers */, 343AAA6C1E83466B00F9D36E /* OIDAuthorizationService+IOS.h in Headers */, @@ -1110,6 +1154,7 @@ 343AAB091E83499100F9D36E /* OIDGrantTypes.h in Headers */, 343AAAFF1E83499100F9D36E /* OIDAuthState.h in Headers */, A6DEABA52018E4BA0022AC32 /* OIDExternalUserAgentSession.h in Headers */, + A6DEABB52018ECF30022AC32 /* OIDEndSessionResponse.h in Headers */, 343AAB0D1E83499100F9D36E /* OIDServiceConfiguration.h in Headers */, A6DEABA02018E4B00022AC32 /* OIDExternalUserAgentRequest.h in Headers */, 343AAAFD1E83499100F9D36E /* OIDAuthorizationService.h in Headers */, @@ -1121,6 +1166,7 @@ A6DEAB9C2018E4AD0022AC32 /* OIDExternalUserAgent.h in Headers */, 343AAB0B1E83499100F9D36E /* OIDScopes.h in Headers */, 343AAB9B1E834A8800F9D36E /* AppAuth.h in Headers */, + A6DEABB12018ECE80022AC32 /* OIDEndSessionRequest.h in Headers */, 343AAB001E83499100F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAB081E83499100F9D36E /* OIDRegistrationRequest.h in Headers */, 34A6632A1E871DD40060B664 /* OIDIDToken.h in Headers */, @@ -1142,6 +1188,7 @@ 343AAB211E83499200F9D36E /* OIDGrantTypes.h in Headers */, 343AAB171E83499200F9D36E /* OIDAuthState.h in Headers */, A6DEABA62018E4BA0022AC32 /* OIDExternalUserAgentSession.h in Headers */, + A6DEABB62018ECF30022AC32 /* OIDEndSessionResponse.h in Headers */, 343AAB251E83499200F9D36E /* OIDServiceConfiguration.h in Headers */, A6DEABA22018E4B60022AC32 /* OIDExternalUserAgentRequest.h in Headers */, 343AAB151E83499200F9D36E /* OIDAuthorizationService.h in Headers */, @@ -1153,6 +1200,7 @@ A6DEAB9D2018E4AD0022AC32 /* OIDExternalUserAgent.h in Headers */, 343AAB231E83499200F9D36E /* OIDScopes.h in Headers */, 343AAB9C1E834A8900F9D36E /* AppAuth.h in Headers */, + A6DEABB22018ECE90022AC32 /* OIDEndSessionRequest.h in Headers */, 343AAB181E83499200F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAB201E83499200F9D36E /* OIDRegistrationRequest.h in Headers */, 34A6632B1E871DD40060B664 /* OIDIDToken.h in Headers */, @@ -1176,6 +1224,7 @@ 343AAAE01E83494400F9D36E /* OIDAuthState+Mac.h in Headers */, 343AAADD1E83494400F9D36E /* OIDRedirectHTTPHandler.h in Headers */, 343AAB3C1E83499200F9D36E /* OIDScopeUtilities.h in Headers */, + A6DEABB32018ECE90022AC32 /* OIDEndSessionRequest.h in Headers */, 343AAB3F1E83499200F9D36E /* OIDTokenRequest.h in Headers */, 55A094D220DFBB12000045D1 /* OIDURLSessionProvider.h in Headers */, 343AAB411E83499200F9D36E /* OIDTokenUtilities.h in Headers */, @@ -1187,6 +1236,7 @@ 343AAB3D1E83499200F9D36E /* OIDServiceConfiguration.h in Headers */, 343AAB351E83499200F9D36E /* OIDErrorUtilities.h in Headers */, 343AAB391E83499200F9D36E /* OIDGrantTypes.h in Headers */, + A6DEABB72018ECF40022AC32 /* OIDEndSessionResponse.h in Headers */, 343AAB341E83499200F9D36E /* OIDError.h in Headers */, 343AAB401E83499200F9D36E /* OIDTokenResponse.h in Headers */, 343AAB3A1E83499200F9D36E /* OIDResponseTypes.h in Headers */, @@ -1693,6 +1743,8 @@ 341310CC1E6F944B00D5DEE5 /* OIDServiceDiscovery.m in Sources */, 341310CA1E6F944B00D5DEE5 /* OIDScopeUtilities.m in Sources */, 340DAE5C1D5821AB00EC285B /* OIDAuthorizationService.m in Sources */, + CF37C06F1F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */, + A6DEAB832017A7030022AC32 /* OIDEndSessionResponse.m in Sources */, 341310CD1E6F944B00D5DEE5 /* OIDTokenRequest.m in Sources */, 341310C91E6F944B00D5DEE5 /* OIDScopes.m in Sources */, 341310CE1E6F944B00D5DEE5 /* OIDTokenResponse.m in Sources */, @@ -1716,6 +1768,7 @@ A6DEABAA2018E5B50022AC32 /* OIDExternalUserAgentIOS.m in Sources */, 341741E01C5D8243000EF209 /* OIDErrorUtilities.m in Sources */, 34A6632D1E871DD40060B664 /* OIDIDToken.m in Sources */, + CF6431F41F228A980075B6B5 /* OIDEndSessionResponse.m in Sources */, 341741EA1C5D8243000EF209 /* OIDTokenUtilities.m in Sources */, 341741E21C5D8243000EF209 /* OIDGrantTypes.m in Sources */, 60140F7C1DE42E1000DA0DC3 /* OIDRegistrationRequest.m in Sources */, @@ -1727,6 +1780,7 @@ 341741E61C5D8243000EF209 /* OIDServiceConfiguration.m in Sources */, 60140F7A1DE4276800DA0DC3 /* OIDClientMetadataParameters.m in Sources */, 341741DE1C5D8243000EF209 /* OIDAuthState.m in Sources */, + CF37C06E1F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */, 341741DD1C5D8243000EF209 /* OIDAuthorizationService.m in Sources */, 345AE747202D526900738D22 /* OIDExternalUserAgentIOSCustomBrowser.m in Sources */, 341741EB1C5D8243000EF209 /* OIDURLQueryComponent.m in Sources */, @@ -1756,6 +1810,7 @@ 34A6638B1E8865090060B664 /* OIDRPProfileCode.m in Sources */, 341742171C5D82D3000EF209 /* OIDAuthorizationRequestTests.m in Sources */, 0396974D1FA827AD003D1FB2 /* OIDURLSessionProviderTests.m in Sources */, + A6CEB11A2007E49C009D492A /* OIDEndSessionRequestTests.m in Sources */, 3417421A1C5D82D3000EF209 /* OIDGrantTypesTests.m in Sources */, 3417421B1C5D82D3000EF209 /* OIDResponseTypesTests.m in Sources */, 60140F831DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m in Sources */, @@ -1774,6 +1829,7 @@ 341AA50A1E7F3A9B00FCA5C6 /* OIDScopesTests.m in Sources */, A5EEF29B20D821970044F470 /* OIDTokenUtilitiesTests.m in Sources */, 341AA50F1E7F3A9B00FCA5C6 /* OIDURLQueryComponentTests.m in Sources */, + A6CEB11B2007E49D009D492A /* OIDEndSessionRequestTests.m in Sources */, 341AA50B1E7F3A9B00FCA5C6 /* OIDServiceConfigurationTests.m in Sources */, 341AA50C1E7F3A9B00FCA5C6 /* OIDServiceDiscoveryTests.m in Sources */, 341AA5071E7F3A9B00FCA5C6 /* OIDAuthStateTests.m in Sources */, @@ -1796,6 +1852,7 @@ 341AA4FD1E7F3A9400FCA5C6 /* OIDScopesTests.m in Sources */, A5EEF29C20D821970044F470 /* OIDTokenUtilitiesTests.m in Sources */, 341AA5021E7F3A9400FCA5C6 /* OIDURLQueryComponentTests.m in Sources */, + A6CEB11C2007E49E009D492A /* OIDEndSessionRequestTests.m in Sources */, 341AA4FE1E7F3A9400FCA5C6 /* OIDServiceConfigurationTests.m in Sources */, 341AA4FF1E7F3A9400FCA5C6 /* OIDServiceDiscoveryTests.m in Sources */, 341AA4FA1E7F3A9400FCA5C6 /* OIDAuthStateTests.m in Sources */, @@ -1829,12 +1886,14 @@ 341310D31E6F944D00D5DEE5 /* OIDError.m in Sources */, 341310DE1E6F944D00D5DEE5 /* OIDTokenRequest.m in Sources */, 341310DF1E6F944D00D5DEE5 /* OIDTokenResponse.m in Sources */, + A6DEAB842017A7040022AC32 /* OIDEndSessionResponse.m in Sources */, 341310DC1E6F944D00D5DEE5 /* OIDServiceConfiguration.m in Sources */, 341310BF1E6F943C00D5DEE5 /* OIDClientMetadataParameters.m in Sources */, 341310E01E6F944D00D5DEE5 /* OIDTokenUtilities.m in Sources */, 341E709A1DE18796004353C1 /* OIDAuthorizationService.m in Sources */, 341310D91E6F944D00D5DEE5 /* OIDResponseTypes.m in Sources */, 341310E11E6F944D00D5DEE5 /* OIDURLQueryComponent.m in Sources */, + CF37C0701F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */, 341310D41E6F944D00D5DEE5 /* OIDErrorUtilities.m in Sources */, 341310D81E6F944D00D5DEE5 /* OIDGrantTypes.m in Sources */, ); @@ -1876,6 +1935,7 @@ A6DEABAF2018E5D80022AC32 /* OIDExternalUserAgentIOS.m in Sources */, 343AAA881E83478900F9D36E /* OIDFieldMapping.m in Sources */, 34A663311E871DD40060B664 /* OIDIDToken.m in Sources */, + A6DEAB862017A7060022AC32 /* OIDEndSessionResponse.m in Sources */, 343AAA841E83478900F9D36E /* OIDAuthState.m in Sources */, 343AAA701E83467D00F9D36E /* OIDAuthState+IOS.m in Sources */, 343AAA921E83478900F9D36E /* OIDTokenResponse.m in Sources */, @@ -1888,6 +1948,7 @@ 343AAA901E83478900F9D36E /* OIDServiceDiscovery.m in Sources */, 343AAA911E83478900F9D36E /* OIDTokenRequest.m in Sources */, 345AE748202D526900738D22 /* OIDExternalUserAgentIOSCustomBrowser.m in Sources */, + A6DEAB8A2017A7140022AC32 /* OIDEndSessionRequest.m in Sources */, 343AAA6F1E83467D00F9D36E /* OIDAuthorizationService+IOS.m in Sources */, 343AAA8F1E83478900F9D36E /* OIDServiceConfiguration.m in Sources */, 343AAA891E83478900F9D36E /* OIDRegistrationResponse.m in Sources */, @@ -1920,6 +1981,7 @@ 343AAA731E8346B400F9D36E /* OIDAuthorizationRequestTests.m in Sources */, 343AAA761E8346B400F9D36E /* OIDGrantTypesTests.m in Sources */, 34A6638E1E8865090060B664 /* OIDRPProfileCode.m in Sources */, + A6CEB11D2007E49F009D492A /* OIDEndSessionRequestTests.m in Sources */, 343AAA741E8346B400F9D36E /* OIDAuthorizationResponseTests.m in Sources */, A5EEF29720D821120044F470 /* OIDTokenUtilitiesTests.m in Sources */, ); @@ -1941,8 +2003,10 @@ 343AAB781E8349B000F9D36E /* OIDScopeUtilities.m in Sources */, 343AAB731E8349B000F9D36E /* OIDRegistrationResponse.m in Sources */, 343AAB701E8349B000F9D36E /* OIDError.m in Sources */, + A6DEAB8B2017A7160022AC32 /* OIDEndSessionRequest.m in Sources */, 343AAB7B1E8349B000F9D36E /* OIDTokenRequest.m in Sources */, 343AAB7C1E8349B000F9D36E /* OIDTokenResponse.m in Sources */, + A6DEAB872017A70B0022AC32 /* OIDEndSessionResponse.m in Sources */, 343AAB791E8349B000F9D36E /* OIDServiceConfiguration.m in Sources */, 343AAB6F1E8349B000F9D36E /* OIDClientMetadataParameters.m in Sources */, 343AAB7D1E8349B000F9D36E /* OIDTokenUtilities.m in Sources */, @@ -1970,8 +2034,10 @@ 343AAB641E8349B000F9D36E /* OIDScopeUtilities.m in Sources */, 343AAB5F1E8349B000F9D36E /* OIDRegistrationResponse.m in Sources */, 343AAB5C1E8349B000F9D36E /* OIDError.m in Sources */, + A6DEAB8C2017A7160022AC32 /* OIDEndSessionRequest.m in Sources */, 343AAB671E8349B000F9D36E /* OIDTokenRequest.m in Sources */, 343AAB681E8349B000F9D36E /* OIDTokenResponse.m in Sources */, + A6DEAB882017A70B0022AC32 /* OIDEndSessionResponse.m in Sources */, 343AAB651E8349B000F9D36E /* OIDServiceConfiguration.m in Sources */, 343AAB5B1E8349B000F9D36E /* OIDClientMetadataParameters.m in Sources */, 343AAB691E8349B000F9D36E /* OIDTokenUtilities.m in Sources */, @@ -2001,6 +2067,7 @@ 343AAB7F1E8349CE00F9D36E /* OIDAuthorizationRequestTests.m in Sources */, 343AAB821E8349CE00F9D36E /* OIDGrantTypesTests.m in Sources */, 34A6638F1E8865090060B664 /* OIDRPProfileCode.m in Sources */, + A6CEB11E2007E4A1009D492A /* OIDEndSessionRequestTests.m in Sources */, 343AAB801E8349CE00F9D36E /* OIDAuthorizationResponseTests.m in Sources */, A5EEF29820D8211A0044F470 /* OIDTokenUtilitiesTests.m in Sources */, ); @@ -2014,6 +2081,7 @@ 343AAADC1E83493D00F9D36E /* OIDAuthState+Mac.m in Sources */, 343AAADA1E83493D00F9D36E /* OIDAuthorizationService+Mac.m in Sources */, 343AAB4C1E8349AF00F9D36E /* OIDRegistrationRequest.m in Sources */, + A6DEAB8D2017A7170022AC32 /* OIDEndSessionRequest.m in Sources */, 343AAB451E8349AF00F9D36E /* OIDAuthorizationService.m in Sources */, 343AAB431E8349AF00F9D36E /* OIDAuthorizationRequest.m in Sources */, 343AAAD91E83493D00F9D36E /* OIDRedirectHTTPHandler.m in Sources */, @@ -2036,6 +2104,7 @@ 34AF736D1FB4E4B40022335F /* OIDURLSessionProvider.m in Sources */, 343AAB561E8349AF00F9D36E /* OIDURLQueryComponent.m in Sources */, 343AAB4E1E8349AF00F9D36E /* OIDResponseTypes.m in Sources */, + A6DEAB892017A70C0022AC32 /* OIDEndSessionResponse.m in Sources */, 343AAB4A1E8349AF00F9D36E /* OIDFieldMapping.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2058,6 +2127,7 @@ 343AAB8D1E8349CF00F9D36E /* OIDAuthorizationRequestTests.m in Sources */, 343AAB901E8349CF00F9D36E /* OIDGrantTypesTests.m in Sources */, 34A663901E8865090060B664 /* OIDRPProfileCode.m in Sources */, + A6CEB11F2007E4A2009D492A /* OIDEndSessionRequestTests.m in Sources */, 343AAB8E1E8349CF00F9D36E /* OIDAuthorizationResponseTests.m in Sources */, A5EEF29920D8211B0044F470 /* OIDTokenUtilitiesTests.m in Sources */, ); @@ -2081,12 +2151,14 @@ 347424041E7F4BA000D3E6D6 /* OIDError.m in Sources */, 3474240F1E7F4BA000D3E6D6 /* OIDTokenRequest.m in Sources */, 347424101E7F4BA000D3E6D6 /* OIDTokenResponse.m in Sources */, + A6DEAB852017A7050022AC32 /* OIDEndSessionResponse.m in Sources */, 3474240D1E7F4BA000D3E6D6 /* OIDServiceConfiguration.m in Sources */, 347424031E7F4BA000D3E6D6 /* OIDClientMetadataParameters.m in Sources */, 347424111E7F4BA000D3E6D6 /* OIDTokenUtilities.m in Sources */, 347424011E7F4BA000D3E6D6 /* OIDAuthorizationService.m in Sources */, 3474240A1E7F4BA000D3E6D6 /* OIDResponseTypes.m in Sources */, 347424121E7F4BA000D3E6D6 /* OIDURLQueryComponent.m in Sources */, + CF37C0711F1FC21A00662E41 /* OIDEndSessionRequest.m in Sources */, 347424051E7F4BA000D3E6D6 /* OIDErrorUtilities.m in Sources */, 347424091E7F4BA000D3E6D6 /* OIDGrantTypes.m in Sources */, ); diff --git a/Source/AppAuth.h b/Source/AppAuth.h index d69772a64..5a6c2f1b1 100644 --- a/Source/AppAuth.h +++ b/Source/AppAuth.h @@ -40,6 +40,8 @@ #import "OIDTokenResponse.h" #import "OIDTokenUtilities.h" #import "OIDURLSessionProvider.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" #if TARGET_OS_TV #elif TARGET_OS_WATCH diff --git a/Source/Framework/AppAuth.h b/Source/Framework/AppAuth.h index 38631081f..dad6479d8 100644 --- a/Source/Framework/AppAuth.h +++ b/Source/Framework/AppAuth.h @@ -48,6 +48,8 @@ FOUNDATION_EXPORT const unsigned char AppAuthVersionString[]; #import #import #import +#import +#import #if TARGET_OS_TV #elif TARGET_OS_WATCH diff --git a/Source/OIDAuthorizationService.h b/Source/OIDAuthorizationService.h index 31c790311..c8fee5358 100644 --- a/Source/OIDAuthorizationService.h +++ b/Source/OIDAuthorizationService.h @@ -21,6 +21,8 @@ @class OIDAuthorization; @class OIDAuthorizationRequest; @class OIDAuthorizationResponse; +@class OIDEndSessionRequest; +@class OIDEndSessionResponse; @class OIDRegistrationRequest; @class OIDRegistrationResponse; @class OIDServiceConfiguration; @@ -47,6 +49,13 @@ typedef void (^OIDDiscoveryCallback)(OIDServiceConfiguration *_Nullable configur typedef void (^OIDAuthorizationCallback)(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error); +/*! @brief Block used as a callback for the end-session request of @c OIDAuthorizationService. + @param endSessionResponse The end-session response, if available. + @param error The error if an error occurred. + */ +typedef void (^OIDEndSessionCallback)(OIDEndSessionResponse *_Nullable endSessionResponse, + NSError *_Nullable error); + /*! @brief Represents the type of block used as a callback for various methods of @c OIDAuthorizationService. @param tokenResponse The token response, if available. @@ -120,6 +129,20 @@ typedef void (^OIDRegistrationCompletion)(OIDRegistrationResponse *_Nullable reg externalUserAgent:(id)externalUserAgent callback:(OIDAuthorizationCallback)callback; +/*! @brief Perform a logout request. + @param request The end-session logout request. + @param externalUserAgent Generic external user-agent that can present user-agent requests. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ ++ (id) + presentEndSessionRequest:(OIDEndSessionRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)callback; + /*! @brief Performs a token request. @param request The token request. @param callback The method called when the request has completed or failed. diff --git a/Source/OIDAuthorizationService.m b/Source/OIDAuthorizationService.m index acd58419b..a55c697d1 100644 --- a/Source/OIDAuthorizationService.m +++ b/Source/OIDAuthorizationService.m @@ -21,6 +21,8 @@ #import "OIDAuthorizationRequest.h" #import "OIDAuthorizationResponse.h" #import "OIDDefines.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" #import "OIDErrorUtilities.h" #import "OIDExternalUserAgent.h" #import "OIDExternalUserAgentSession.h" @@ -92,9 +94,14 @@ - (void)cancel { }]; } -- (BOOL)shouldHandleURL:(NSURL *)URL { +/*! @brief Does the redirection URL equal another URL down to the path component? + @param URL The first redirect URI to compare. + @param redirectonURL The second redirect URI to compare. + @return YES if the URLs match down to the path level (query params are ignored). + */ ++ (BOOL)URL:(NSURL *)URL matchesRedirectonURL:(NSURL *)redirectonURL { NSURL *standardizedURL = [URL standardizedURL]; - NSURL *standardizedRedirectURL = [_request.redirectURL standardizedURL]; + NSURL *standardizedRedirectURL = [redirectonURL standardizedURL]; return [standardizedURL.scheme caseInsensitiveCompare:standardizedRedirectURL.scheme] == NSOrderedSame && OIDIsEqualIncludingNil(standardizedURL.user, standardizedRedirectURL.user) @@ -104,6 +111,10 @@ - (BOOL)shouldHandleURL:(NSURL *)URL { && OIDIsEqualIncludingNil(standardizedURL.path, standardizedRedirectURL.path); } +- (BOOL)shouldHandleURL:(NSURL *)URL { + return [[self class] URL:URL matchesRedirectonURL:_request.redirectURL]; +} + - (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { // rejects URLs that don't match redirect (these may be completely unrelated to the authorization) if (![self shouldHandleURL:URL]) { @@ -178,6 +189,121 @@ - (void)didFinishWithResponse:(nullable OIDAuthorizationResponse *)response @end +@interface OIDEndSessionImplementation : NSObject { + // private variables + OIDEndSessionRequest *_request; + id _externalUserAgent; + OIDEndSessionCallback _pendingEndSessionCallback; +} +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + NS_DESIGNATED_INITIALIZER; +@end + + +@implementation OIDEndSessionImplementation + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request { + self = [super init]; + if (self) { + _request = [request copy]; + } + return self; +} + +- (void)presentAuthorizationWithExternalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)authorizationFlowCallback { + _externalUserAgent = externalUserAgent; + _pendingEndSessionCallback = authorizationFlowCallback; + BOOL authorizationFlowStarted = + [_externalUserAgent presentExternalUserAgentRequest:_request session:self]; + if (!authorizationFlowStarted) { + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open Safari."]; + [self didFinishWithResponse:nil error:safariError]; + } +} + +- (void)cancel { + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + NSError *error = [OIDErrorUtilities + errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:nil]; + [self didFinishWithResponse:nil error:error]; + }]; +} + +- (BOOL)shouldHandleURL:(NSURL *)URL { + // The logic of when to handle the URL is the same as for authorization requests: should match + // down to the path component. + return [[OIDAuthorizationSession class] URL:URL + matchesRedirectonURL:_request.postLogoutRedirectURL]; +} + +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { + // rejects URLs that don't match redirect (these may be completely unrelated to the authorization) + if (![self shouldHandleURL:URL]) { + return NO; + } + // checks for an invalid state + if (!_pendingEndSessionCallback) { + [NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow + format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil]; + } + + + NSError *error; + OIDEndSessionResponse *response = nil; + + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:URL]; + response = [[OIDEndSessionResponse alloc] initWithRequest:_request + parameters:query.dictionaryValue]; + + // verifies that the state in the response matches the state in the request, or both are nil + if (!OIDIsEqualIncludingNil(_request.state, response.state)) { + NSMutableDictionary *userInfo = [query.dictionaryValue mutableCopy]; + userInfo[NSLocalizedDescriptionKey] = + [NSString stringWithFormat:@"State mismatch, expecting %@ but got %@ in authorization " + "response %@", + _request.state, + response.state, + response]; + response = nil; + error = [NSError errorWithDomain:OIDOAuthAuthorizationErrorDomain + code:OIDErrorCodeOAuthAuthorizationClientError + userInfo:userInfo]; + } + + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + [self didFinishWithResponse:response error:error]; + }]; + + return YES; +} + +- (void)failExternalUserAgentFlowWithError:(NSError *)error { + [self didFinishWithResponse:nil error:error]; +} + +/*! @brief Invokes the pending callback and performs cleanup. + @param response The authorization response, if any to return to the callback. + @param error The error, if any, to return to the callback. + */ +- (void)didFinishWithResponse:(nullable OIDEndSessionResponse *)response + error:(nullable NSError *)error { + OIDEndSessionCallback callback = _pendingEndSessionCallback; + _pendingEndSessionCallback = nil; + _externalUserAgent = nil; + if (callback) { + callback(response, error); + } +} + +@end + @implementation OIDAuthorizationService + (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL @@ -272,6 +398,16 @@ + (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL return flowSession; } ++ (id) + presentEndSessionRequest:(OIDEndSessionRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)callback { + OIDEndSessionImplementation *flowSession = + [[OIDEndSessionImplementation alloc] initWithRequest:request]; + [flowSession presentAuthorizationWithExternalUserAgent:externalUserAgent callback:callback]; + return flowSession; +} + #pragma mark - Token Endpoint + (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback { diff --git a/Source/OIDEndSessionRequest.h b/Source/OIDEndSessionRequest.h new file mode 100644 index 000000000..4087e9fa9 --- /dev/null +++ b/Source/OIDEndSessionRequest.h @@ -0,0 +1,107 @@ +/*! @file OIDEndSessionRequest.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "OIDExternalUserAgentRequest.h" + +@class OIDServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDEndSessionRequest : NSObject + + +/*! @brief The service's configuration. + @remarks This configuration specifies how to connect to a particular OAuth provider. + Configurations may be created manually, or via an OpenID Connect Discovery Document. + */ +@property(nonatomic, readonly) OIDServiceConfiguration *configuration; + +/*! @brief The client's redirect URI. + @remarks post_logout_redirect_uri + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSURL *postLogoutRedirectURL; + +/*! @brief Previously issued ID Token passed to the end session endpoint as a hint about the End-User's current authenticated + session with the Client + @remarks id_token_hint + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSString *idTokenHint; + +/*! @brief An opaque value used by the client to maintain state between the request and callback. + @remarks state + @discussion If this value is not explicitly set, this library will automatically add state and + perform appropriate validation of the state in the authorization response. It is recommended + that the default implementation of this parameter be used wherever possible. Typically used + to prevent CSRF attacks, as recommended in RFC6819 Section 5.3.5. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSString *state; + +/*! @brief The client's additional authorization parameters. + @see https://tools.ietf.org/html/rfc6749#section-3.1 + */ +@property(nonatomic, readonly, nullable) NSDictionary *additionalParameters; + +/*! @internal + @brief Unavailable. Please use @c initWithConfiguration:clientId:scopes:redirectURL:additionalParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Creates an authorization request with opinionated defaults (a secure @c state). + @param configuration The service's configuration. + @param idTokenHint The previously issued ID Token + @param postLogoutRedirectURL The client's post-logout redirect URI. + callback. + @param additionalParameters The client's additional authorization parameters. +*/ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Designated initializer. + @param configuration The service's configuration. + @param idTokenHint The previously issued ID Token + @param postLogoutRedirectURL The client's post-logout redirect URI. + @param state An opaque value used by the client to maintain state between the request and + callback. + @param additionalParameters The client's additional authorization parameters. + */ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + state:(NSString *)state + additionalParameters:(nullable NSDictionary *)additionalParameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Constructs the request URI by adding the request parameters to the query component of the + authorization endpoint URI using the "application/x-www-form-urlencoded" format. + @return A URL representing the authorization request. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +- (NSURL *)endSessionRequestURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/OIDEndSessionRequest.m b/Source/OIDEndSessionRequest.m new file mode 100644 index 000000000..1e9eb0e22 --- /dev/null +++ b/Source/OIDEndSessionRequest.m @@ -0,0 +1,190 @@ +/*! @file OIDEndSessionRequest.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDEndSessionRequest.h" + +#import "OIDDefines.h" +#import "OIDTokenUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDURLQueryComponent.h" + +/*! @brief The key for the @c configuration property for @c NSSecureCoding + */ +static NSString *const kConfigurationKey = @"configuration"; + +/*! @brief Key used to encode the @c state property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kStateKey = @"state"; + +/*! @brief Key used to encode the @c postLogoutRedirectURL property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kPostLogoutRedirectURLKey = @"post_logout_redirect_uri"; + +/*! @brief Key used to encode the @c idTokenHint property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kIdTokenHintKey = @"id_token_hint"; + +/*! @brief Key used to encode the @c additionalParameters property for @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +/*! @brief Number of random bytes generated for the @state. + */ +static NSUInteger const kStateSizeBytes = 32; + +/*! @brief Assertion text for missing end_session_endpoint. + */ +static NSString *const OIDMissingEndSessionEndpointMessage = +@"The service configuration is missing an end_session_endpoint."; + +@implementation OIDEndSessionRequest + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithConfiguration: + idTokenHint: + postLogoutRedirectURL: + additionalParameters:) + ) + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + state:(NSString *)state + additionalParameters:(NSDictionary *)additionalParameters +{ + self = [super init]; + if (self) { + _configuration = [configuration copy]; + _idTokenHint = [idTokenHint copy]; + _postLogoutRedirectURL = [postLogoutRedirectURL copy]; + _state = [state copy]; + _additionalParameters = + [[NSDictionary alloc] initWithDictionary:additionalParameters copyItems:YES]; + } + return self; +} + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + additionalParameters:(NSDictionary *)additionalParameters +{ + return [self initWithConfiguration:configuration + idTokenHint:idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + state:[[self class] generateState] + additionalParameters:additionalParameters]; +} +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDServiceConfiguration *configuration = [aDecoder decodeObjectOfClass:[OIDServiceConfiguration class] forKey:kConfigurationKey]; + + NSString *idTokenHint = [aDecoder decodeObjectOfClass:[NSString class] forKey:kIdTokenHintKey]; + NSURL *postLogoutRedirectURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kPostLogoutRedirectURLKey]; + NSString *state = [aDecoder decodeObjectOfClass:[NSString class] forKey:kStateKey]; + NSSet *additionalParameterCodingClasses = [NSSet setWithArray:@[ + [NSDictionary class], + [NSString class] + ]]; + NSDictionary *additionalParameters = [aDecoder decodeObjectOfClasses:additionalParameterCodingClasses + forKey:kAdditionalParametersKey]; + + self = [self initWithConfiguration:configuration + idTokenHint:idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + state:state + additionalParameters:additionalParameters]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_configuration forKey:kConfigurationKey]; + [aCoder encodeObject:_idTokenHint forKey:kIdTokenHintKey]; + [aCoder encodeObject:_postLogoutRedirectURL forKey:kPostLogoutRedirectURLKey]; + [aCoder encodeObject:_state forKey:kStateKey]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, request: %@>", + NSStringFromClass([self class]), + (void *)self, + self.endSessionRequestURL]; +} + ++ (nullable NSString *)generateState { + return [OIDTokenUtilities randomURLSafeStringWithSize:kStateSizeBytes]; +} + +#pragma mark - OIDExternalUserAgentRequest + +- (NSURL*)externalUserAgentRequestURL { + return [self endSessionRequestURL]; +} + +- (NSString *)redirectScheme { + return [_postLogoutRedirectURL scheme]; +} + +#pragma mark - + +- (NSURL *)endSessionRequestURL { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; + + // Add any additional parameters the client has specified. + [query addParameters:_additionalParameters]; + + // Add optional parameters, as applicable. + if (_idTokenHint) { + [query addParameter:kIdTokenHintKey value:_idTokenHint]; + } + + if (_postLogoutRedirectURL) { + [query addParameter:kPostLogoutRedirectURLKey value:_postLogoutRedirectURL.absoluteString]; + } + + if (_state) { + [query addParameter:kStateKey value:_state]; + } + + NSAssert(_configuration.endSessionEndpoint, OIDMissingEndSessionEndpointMessage); + + // Construct the URL + return [query URLByReplacingQueryInURL:_configuration.endSessionEndpoint]; +} + +@end diff --git a/Source/OIDEndSessionResponse.h b/Source/OIDEndSessionResponse.h new file mode 100644 index 000000000..ab69b9305 --- /dev/null +++ b/Source/OIDEndSessionResponse.h @@ -0,0 +1,64 @@ +/*! @file OIDEndSessionResponse.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@class OIDEndSessionRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents the response to an End Session request. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ + +@interface OIDEndSessionResponse : NSObject + +/*! @brief The request which was serviced. + */ +@property(nonatomic, readonly) OIDEndSessionRequest *request; + +/*! @brief REQUIRED if the "state" parameter was present in the client end-session request. The + exact value received from the client. + @remarks state + */ +@property(nonatomic, readonly, nullable) NSString *state; + +/*! @brief Additional parameters returned from the end session endpoint. + */ +@property(nonatomic, readonly, nullable) + NSDictionary *> *additionalParameters; + +/*! @internal + @brief Unavailable. Please use initWithParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param request The serviced request. + @param parameters The decoded parameters returned from the End Session Endpoint. + @remarks Known parameters are extracted from the @c parameters parameter and the normative + properties are populated. Non-normative parameters are placed in the + @c #additionalParameters dictionary. + */ +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + parameters:(NSDictionary *> *)parameters + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/OIDEndSessionResponse.m b/Source/OIDEndSessionResponse.m new file mode 100644 index 000000000..bedf0cd93 --- /dev/null +++ b/Source/OIDEndSessionResponse.m @@ -0,0 +1,118 @@ +/*! @file OIDEndSessionResponse.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDEndSessionResponse.h" + +#import "OIDDefines.h" +#import "OIDEndSessionRequest.h" +#import "OIDFieldMapping.h" + +/*! @brief The key for the @c state property in the incoming parameters and for @c NSSecureCoding. + */ +static NSString *const kStateKey = @"state"; + +/*! @brief Key used to encode the @c request property for @c NSSecureCoding + */ +static NSString *const kRequestKey = @"request"; + +/*! @brief Key used to encode the @c additionalParameters property for + @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +@implementation OIDEndSessionResponse + +#pragma mark - Initializers + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + parameters:(NSDictionary *> *)parameters { + self = [super init]; + if (self) { + _request = [request copy]; + NSDictionary *> *additionalParameters = + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:parameters + instance:self]; + _additionalParameters = additionalParameters; + } + return self; +} + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + fieldMap[kStateKey] = + [[OIDFieldMapping alloc] initWithName:@"_state" type:[NSString class]]; + }); + return fieldMap; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDEndSessionRequest *request = + [aDecoder decodeObjectOfClass:[OIDEndSessionRequest class] forKey:kRequestKey]; + self = [self initWithRequest:request parameters:@{ }]; + if (self) { + [OIDFieldMapping decodeWithCoder:aDecoder map:[[self class] fieldMap] instance:self]; + _additionalParameters = [aDecoder decodeObjectOfClasses:[OIDFieldMapping JSONTypes] + forKey:kAdditionalParametersKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_request forKey:kRequestKey]; + [OIDFieldMapping encodeWithCoder:aCoder map:[[self class] fieldMap] instance:self]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, state: \"%@\", " + "additionalParameters: %@, request: %@>", + NSStringFromClass([self class]), + (void *)self, + _state, + _additionalParameters, + _request]; +} +@end diff --git a/Source/OIDServiceConfiguration.h b/Source/OIDServiceConfiguration.h index 2221a1da7..a072a478f 100644 --- a/Source/OIDServiceConfiguration.h +++ b/Source/OIDServiceConfiguration.h @@ -50,6 +50,10 @@ typedef void (^OIDServiceConfigurationCreated) */ @property(nonatomic, readonly, nullable) NSURL *registrationEndpoint; +/*! @brief The end session logout endpoint URI. + */ +@property(nonatomic, readonly, nullable) NSURL *endSessionEndpoint; + /*! @brief The discovery document. */ @property(nonatomic, readonly, nullable) OIDServiceDiscovery *discoveryDocument; @@ -92,6 +96,18 @@ typedef void (^OIDServiceConfigurationCreated) issuer:(nullable NSURL *)issuer registrationEndpoint:(nullable NSURL *)registrationEndpoint; +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + @param issuer The OpenID Connect issuer. + @param registrationEndpoint The dynamic client registration endpoint URI. + @param endSessionEndpoint The end session endpoint (logout) URI. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint; + /*! @param discoveryDocument The discovery document from which to extract the required OAuth configuration. */ diff --git a/Source/OIDServiceConfiguration.m b/Source/OIDServiceConfiguration.m index 517c955bd..ca48a8c33 100644 --- a/Source/OIDServiceConfiguration.m +++ b/Source/OIDServiceConfiguration.m @@ -38,6 +38,10 @@ */ static NSString *const kRegistrationEndpointKey = @"registrationEndpoint"; +/*! @brief The key for the @c endSessionEndpoint property. + */ +static NSString *const kEndSessionEndpointKey = @"endSessionEndpoint"; + /*! @brief The key for the @c discoveryDocument property. */ static NSString *const kDiscoveryDocumentKey = @"discoveryDocument"; @@ -50,6 +54,7 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:(NSURL *)tokenEndpoint issuer:(nullable NSURL *)issuer registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint discoveryDocument:(nullable OIDServiceDiscovery *)discoveryDocument NS_DESIGNATED_INITIALIZER; @@ -67,6 +72,7 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:(NSURL *)tokenEndpoint issuer:(nullable NSURL *)issuer registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable OIDServiceDiscovery *)endSessionEndpoint discoveryDocument:(nullable OIDServiceDiscovery *)discoveryDocument { self = [super init]; @@ -75,6 +81,7 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint _tokenEndpoint = [tokenEndpoint copy]; _issuer = [issuer copy]; _registrationEndpoint = [registrationEndpoint copy]; + _endSessionEndpoint = [endSessionEndpoint copy]; _discoveryDocument = [discoveryDocument copy]; } return self; @@ -86,6 +93,7 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:tokenEndpoint issuer:nil registrationEndpoint:nil + endSessionEndpoint:nil discoveryDocument:nil]; } @@ -96,6 +104,7 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:tokenEndpoint issuer:nil registrationEndpoint:registrationEndpoint + endSessionEndpoint:nil discoveryDocument:nil]; } @@ -106,6 +115,7 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:tokenEndpoint issuer:issuer registrationEndpoint:nil + endSessionEndpoint:nil discoveryDocument:nil]; } @@ -117,6 +127,20 @@ - (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint tokenEndpoint:tokenEndpoint issuer:issuer registrationEndpoint:registrationEndpoint + endSessionEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:issuer + registrationEndpoint:registrationEndpoint + endSessionEndpoint:endSessionEndpoint discoveryDocument:nil]; } @@ -125,6 +149,7 @@ - (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *) discoveryDocum tokenEndpoint:discoveryDocument.tokenEndpoint issuer:discoveryDocument.issuer registrationEndpoint:discoveryDocument.registrationEndpoint + endSessionEndpoint:discoveryDocument.endSessionEndpoint discoveryDocument:discoveryDocument]; } @@ -153,6 +178,8 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { forKey:kIssuerKey]; NSURL *registrationEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kRegistrationEndpointKey]; + NSURL *endSessionEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kEndSessionEndpointKey]; // We don't accept nil authorizationEndpoints or tokenEndpoints. if (!authorizationEndpoint || !tokenEndpoint) { return nil; @@ -165,6 +192,7 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { tokenEndpoint:tokenEndpoint issuer:issuer registrationEndpoint:registrationEndpoint + endSessionEndpoint:endSessionEndpoint discoveryDocument:discoveryDocument]; } @@ -174,6 +202,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_issuer forKey:kIssuerKey]; [aCoder encodeObject:_registrationEndpoint forKey:kRegistrationEndpointKey]; [aCoder encodeObject:_discoveryDocument forKey:kDiscoveryDocumentKey]; + [aCoder encodeObject:_endSessionEndpoint forKey:kEndSessionEndpointKey]; } #pragma mark - description @@ -181,10 +210,11 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { - (NSString *)description { return [NSString stringWithFormat: @"OIDServiceConfiguration authorizationEndpoint: %@, tokenEndpoint: %@, " - "registrationEndpoint: %@, discoveryDocument: [%@]", + "registrationEndpoint: %@, endSessionEndpoint: %@, discoveryDocument: [%@]", _authorizationEndpoint, _tokenEndpoint, _registrationEndpoint, + _endSessionEndpoint, _discoveryDocument]; } diff --git a/Source/OIDServiceDiscovery.h b/Source/OIDServiceDiscovery.h index 982d71005..577700834 100644 --- a/Source/OIDServiceDiscovery.h +++ b/Source/OIDServiceDiscovery.h @@ -78,6 +78,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, readonly, nullable) NSURL *registrationEndpoint; +/* @brief OPTIONAL. URL of the OP's RP-Initiated Logout endpoint. + @remarks end_session_endpoint + @seealso http://openid.net/specs/openid-connect-session-1_0.html#OPMetadata + */ +@property(nonatomic, readonly, nullable) NSURL *endSessionEndpoint; + /*! @brief RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. The server MUST support the openid scope value. Servers MAY choose not to advertise some supported scope values even when this parameter is used, although those diff --git a/Source/OIDServiceDiscovery.m b/Source/OIDServiceDiscovery.m index bbd11d738..ca81108a8 100644 --- a/Source/OIDServiceDiscovery.m +++ b/Source/OIDServiceDiscovery.m @@ -30,6 +30,7 @@ static NSString *const kUserinfoEndpointKey = @"userinfo_endpoint"; static NSString *const kJWKSURLKey = @"jwks_uri"; static NSString *const kRegistrationEndpointKey = @"registration_endpoint"; +static NSString *const kEndSessionEndpointKey = @"end_session_endpoint"; static NSString *const kScopesSupportedKey = @"scopes_supported"; static NSString *const kResponseTypesSupportedKey = @"response_types_supported"; static NSString *const kResponseModesSupportedKey = @"response_modes_supported"; @@ -232,6 +233,10 @@ - (nullable NSURL *)registrationEndpoint { return [NSURL URLWithString:_discoveryDictionary[kRegistrationEndpointKey]]; } +- (nullable NSURL *)endSessionEndpoint { + return [NSURL URLWithString:_discoveryDictionary[kEndSessionEndpointKey]]; +} + - (nullable NSArray *)scopesSupported { return _discoveryDictionary[kScopesSupportedKey]; } diff --git a/UnitTests/OIDEndSessionRequestTests.h b/UnitTests/OIDEndSessionRequestTests.h new file mode 100644 index 000000000..48f902757 --- /dev/null +++ b/UnitTests/OIDEndSessionRequestTests.h @@ -0,0 +1,31 @@ +/*! @file OIDServiceDiscoveryTests.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@class OIDEndSessionRequest; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDEndSessionRequestTests : XCTestCase + ++ (OIDEndSessionRequest *)testInstance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/UnitTests/OIDEndSessionRequestTests.m b/UnitTests/OIDEndSessionRequestTests.m new file mode 100644 index 000000000..4aadc6b57 --- /dev/null +++ b/UnitTests/OIDEndSessionRequestTests.m @@ -0,0 +1,124 @@ +/*! @file OIDServiceDiscoveryTests.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDEndSessionRequestTests.h" + +#import "OIDServiceDiscoveryTests.h" +#import "Source/OIDEndSessionRequest.h" +#import "Source/OIDServiceConfiguration.h" +#import "Source/OIDServiceDiscovery.h" + +/*! @brief Test value for the @c redirectURL property. + */ +static NSString *const kTestRedirectURL = @"http://www.google.com/"; + +/*! @brief Test key for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterKey = @"A"; + +/*! @brief Test value for the @c additionalParameters property. + */ +static NSString *const kTestAdditionalParameterValue = @"1"; + +/*! @brief Test value for the @c state property. + */ +static NSString *const kTestState = @"State"; + +/*! @brief Test value for the @c idTokenHint property. + */ +static NSString *const kTestIdTokenHint = @"id-token-hint"; + +@implementation OIDEndSessionRequestTests + ++ (OIDEndSessionRequest *)testInstance { + NSDictionary *additionalParameters = + @{ kTestAdditionalParameterKey : kTestAdditionalParameterValue }; + + OIDServiceDiscovery *discoveryDocument = [[OIDServiceDiscovery alloc] initWithDictionary:[OIDServiceDiscoveryTests completeServiceDiscoveryDictionary] error:nil]; + OIDServiceConfiguration *configuration = [[OIDServiceConfiguration alloc] initWithDiscoveryDocument:discoveryDocument]; + + return [[OIDEndSessionRequest alloc] initWithConfiguration:configuration + idTokenHint:kTestIdTokenHint + postLogoutRedirectURL:[NSURL URLWithString:kTestRedirectURL] + state:kTestState + additionalParameters:additionalParameters]; +} + +/*! @brief Tests the @c NSCopying implementation by round-tripping an instance through the copying + process and checking to make sure the source and destination instances are equivalent. + */ +- (void)testCopying { + OIDEndSessionRequest *request = [[self class] testInstance]; + + XCTAssertEqualObjects(request.idTokenHint, kTestIdTokenHint); + XCTAssertEqualObjects(request.postLogoutRedirectURL, [NSURL URLWithString:kTestRedirectURL]); + XCTAssertEqualObjects(request.state, kTestState); + XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], + kTestAdditionalParameterValue); + + OIDEndSessionRequest *requestCopy = [request copy]; + + XCTAssertNotNil(requestCopy.configuration); + XCTAssertEqualObjects(requestCopy.configuration, request.configuration); + XCTAssertEqualObjects(requestCopy.postLogoutRedirectURL, request.postLogoutRedirectURL); + XCTAssertEqualObjects(requestCopy.state, request.state); + XCTAssertEqualObjects(requestCopy.idTokenHint, request.idTokenHint); +} + +/*! @brief Tests the @c NSSecureCoding by round-tripping an instance through the coding process and + checking to make sure the source and destination instances are equivalent. + */ +- (void)testSecureCoding { + OIDEndSessionRequest *request = [[self class] testInstance]; + + XCTAssertEqualObjects(request.idTokenHint, kTestIdTokenHint); + XCTAssertEqualObjects(request.postLogoutRedirectURL, [NSURL URLWithString:kTestRedirectURL]); + XCTAssertEqualObjects(request.state, kTestState); + XCTAssertEqualObjects(request.additionalParameters[kTestAdditionalParameterKey], + kTestAdditionalParameterValue); + + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:request]; + OIDEndSessionRequest *requestCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + + XCTAssertNotNil(requestCopy.configuration); + XCTAssertEqualObjects(requestCopy.configuration.authorizationEndpoint, + request.configuration.authorizationEndpoint); + XCTAssertEqualObjects(requestCopy.postLogoutRedirectURL, request.postLogoutRedirectURL); + XCTAssertEqualObjects(requestCopy.state, request.state); + XCTAssertEqualObjects(requestCopy.idTokenHint, request.idTokenHint); +} + +- (void)testLogoutRequestURL { + OIDEndSessionRequest *request = [[self class] testInstance]; + NSURL *endSessionRequestURL = request.endSessionRequestURL; + + NSURLComponents *components = [NSURLComponents componentsWithString:endSessionRequestURL.absoluteString]; + + XCTAssertTrue([endSessionRequestURL.absoluteString hasPrefix:@"https://www.example.com/logout"]); + + NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; + for (NSURLQueryItem *queryItem in components.queryItems) { + query[queryItem.name] = queryItem.value; + } + + XCTAssertEqualObjects(query[@"state"], kTestState); + XCTAssertEqualObjects(query[@"id_token_hint"], kTestIdTokenHint); + XCTAssertEqualObjects(query[@"post_logout_redirect_uri"], kTestRedirectURL); +} + +@end diff --git a/UnitTests/OIDServiceDiscoveryTests.m b/UnitTests/OIDServiceDiscoveryTests.m index f030019ce..b9a13ce86 100644 --- a/UnitTests/OIDServiceDiscoveryTests.m +++ b/UnitTests/OIDServiceDiscoveryTests.m @@ -39,6 +39,7 @@ static NSString *const kUserinfoEndpointKey = @"userinfo_endpoint"; static NSString *const kJWKSURLKey = @"jwks_uri"; static NSString *const kRegistrationEndpointKey = @"registration_endpoint"; +static NSString *const kEndSessionEndpointKey = @"end_session_endpoint"; static NSString *const kScopesSupportedKey = @"scopes_supported"; static NSString *const kResponseTypesSupportedKey = @"response_types_supported"; static NSString *const kResponseModesSupportedKey = @"response_modes_supported"; @@ -102,6 +103,7 @@ + (NSDictionary *)completeServiceDiscoveryDictionary { kUserinfoEndpointKey : @"User Info Endpoint", kJWKSURLKey : @"http://www.example.com/jwks", kRegistrationEndpointKey : @"Registration Endpoint", + kEndSessionEndpointKey : @"https://www.example.com/logout", kScopesSupportedKey : @"Scopes Supported", kResponseTypesSupportedKey : @"Response Types Supported", kResponseModesSupportedKey : @"Response Modes Supported", @@ -487,6 +489,7 @@ - (void)testField_##_field_ { TestURLFieldBackedBy(userinfoEndpoint, kUserinfoEndpointKey, kTestURL) TestURLFieldBackedBy(jwksURL, kJWKSURLKey, kTestURL) TestURLFieldBackedBy(registrationEndpoint, kRegistrationEndpointKey, kTestURL) +TestURLFieldBackedBy(endSessionEndpoint, kEndSessionEndpointKey, kTestURL) TestFieldBackedBy(scopesSupported, kScopesSupportedKey, @"Scopes Supported") TestFieldBackedBy(responseTypesSupported, kResponseTypesSupportedKey, @"Response Types Supported") TestFieldBackedBy(responseModesSupported, kResponseModesSupportedKey, @"Response Modes Supported")