From bbd1dc52fe47ded6b8498fed38ae2cf5f86832c5 Mon Sep 17 00:00:00 2001 From: Ildar Abdullin Date: Tue, 30 Jul 2024 10:04:17 -0700 Subject: [PATCH] OKTA-748810 - Crash/Non-fatal - --- .github/workflows/okta-oidc.yml | 14 +- OktaOidc.podspec | 2 +- Sources/AppAuth/OktaUserAgent.m | 2 +- .../AppAuth/macOS/OKTRedirectHTTPHandler.m | 24 +++- .../OKTRedirectHTTPHandlerTests.m | 132 ++++++++++++++++++ okta-oidc.xcodeproj/project.pbxproj | 12 +- 6 files changed, 170 insertions(+), 16 deletions(-) create mode 100644 Tests/AppAuthTests/OKTRedirectHTTPHandlerTests.m diff --git a/.github/workflows/okta-oidc.yml b/.github/workflows/okta-oidc.yml index b43e209ec23..7a176d6ba7d 100644 --- a/.github/workflows/okta-oidc.yml +++ b/.github/workflows/okta-oidc.yml @@ -8,27 +8,27 @@ on: jobs: UnitTests: - runs-on: macos-11 + runs-on: macos-latest env: - DEVELOPER_DIR: /Applications/Xcode_13.1.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer steps: - uses: actions/checkout@v2 - name: iOS - run: set -o pipefail && xcodebuild -project okta-oidc.xcodeproj -scheme "okta-oidc-ios" -destination "platform=iOS Simulator,OS=latest,name=iPhone 11" clean test | xcpretty + run: set -o pipefail && xcodebuild -project okta-oidc.xcodeproj -scheme "okta-oidc-ios" -destination "platform=iOS Simulator,OS=latest,name=iPhone 15" clean test | xcpretty - name: macOS run: set -o pipefail && xcodebuild -project okta-oidc.xcodeproj -scheme "okta-oidc-mac" -destination "platform=macOS" clean test | xcpretty - name: Swift run: swift test -v UITests: - runs-on: macos-11 + runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: iOS - run: set -o pipefail && xcodebuild -project okta-oidc.xcodeproj -scheme "okta-oidc" -destination "platform=iOS Simulator,OS=latest,name=iPhone 11" clean test | xcpretty + run: set -o pipefail && xcodebuild -project okta-oidc.xcodeproj -scheme "okta-oidc" -destination "platform=iOS Simulator,OS=latest,name=iPhone 15" clean test | xcpretty PackageValidation: - runs-on: macos-11 + runs-on: macos-latest env: - DEVELOPER_DIR: /Applications/Xcode_13.1.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer steps: - uses: actions/checkout@v2 - name: Cocoapods diff --git a/OktaOidc.podspec b/OktaOidc.podspec index 964ecf4adbf..4691e7c1f8b 100644 --- a/OktaOidc.podspec +++ b/OktaOidc.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'OktaOidc' - s.version = '3.11.5' + s.version = '3.11.6' s.summary = 'SDK to easily integrate AppAuth with Okta' s.description = <<-DESC Integrate your native app with Okta using the AppAuth library. diff --git a/Sources/AppAuth/OktaUserAgent.m b/Sources/AppAuth/OktaUserAgent.m index 159513718e5..d2621e07b22 100644 --- a/Sources/AppAuth/OktaUserAgent.m +++ b/Sources/AppAuth/OktaUserAgent.m @@ -22,7 +22,7 @@ +(void)setUserAgentValue:(NSString*)value { } +(NSString*)userAgentVersion { - return @"3.11.5"; + return @"3.11.6"; } +(NSString*)userAgentHeaderKey { diff --git a/Sources/AppAuth/macOS/OKTRedirectHTTPHandler.m b/Sources/AppAuth/macOS/OKTRedirectHTTPHandler.m index 587a21439a5..983ba47dbe4 100644 --- a/Sources/AppAuth/macOS/OKTRedirectHTTPHandler.m +++ b/Sources/AppAuth/macOS/OKTRedirectHTTPHandler.m @@ -127,14 +127,28 @@ - (void)stopHTTPListener { _httpServ = nil; } +- (BOOL)isOptionsHTTPServerRequest:(HTTPServerRequest *)request { + CFStringRef method = CFHTTPMessageCopyRequestMethod(request.request); + if (method == nil) { + return NO; + } + + BOOL isOptionsRequest = CFStringCompare(method, (__bridge CFStringRef)@"OPTIONS", kCFCompareCaseInsensitive) == kCFCompareEqualTo; + CFRelease(method); + + return isOptionsRequest; +} + - (void)HTTPConnection:(HTTPConnection *)conn didReceiveRequest:(HTTPServerRequest *)mess { // Handle private network preflight // https://developer.chrome.com/blog/private-network-access-preflight/ - CFStringRef method = CFHTTPMessageCopyRequestMethod(mess.request); - BOOL isOptionsRequest = CFStringCompare(method, (__bridge CFStringRef)@"OPTIONS", 0) == kCFCompareEqualTo; + BOOL isOptionsRequest = [self isOptionsHTTPServerRequest:mess]; CFStringRef requestPrivateNetwork = isOptionsRequest ? CFHTTPMessageCopyHeaderFieldValue(mess.request, (__bridge CFStringRef)@"Access-Control-Request-Private-Network") : nil; BOOL doesRequestPrivateNetwork = requestPrivateNetwork != nil && CFStringCompare(requestPrivateNetwork, (__bridge CFStringRef)@"true", 0) == kCFCompareEqualTo; + if (requestPrivateNetwork != nil) { + CFRelease(requestPrivateNetwork); + } if (isOptionsRequest && doesRequestPrivateNetwork) { CFHTTPMessageRef response = CFHTTPMessageCreateResponse( kCFAllocatorDefault, @@ -159,9 +173,13 @@ - (void)HTTPConnection:(HTTPConnection *)conn didReceiveRequest:(HTTPServerReque return; } + BOOL handled = NO; // Sends URL to AppAuth. CFURLRef url = CFHTTPMessageCopyRequestURL(mess.request); - BOOL handled = [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:(__bridge NSURL *)url]; + if (url != nil) { + handled = [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:(__bridge NSURL *)url]; + CFRelease(url); + } // Stops listening to further requests after the first valid authorization response. if (handled) { diff --git a/Tests/AppAuthTests/OKTRedirectHTTPHandlerTests.m b/Tests/AppAuthTests/OKTRedirectHTTPHandlerTests.m new file mode 100644 index 00000000000..1c0c0cfcdf0 --- /dev/null +++ b/Tests/AppAuthTests/OKTRedirectHTTPHandlerTests.m @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024-Present, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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 "OKTRedirectHTTPHandler.h" +#import "OKTLoopbackHTTPServer.h" + +@interface OKTRedirectHTTPHandlerTests : XCTestCase + +@property BOOL resumeExternalUserAgentFlowWithURLCalled; + +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL; + +@end + +@interface OKTRedirectHTTPHandler(Private) + +- (BOOL)isOptionsHTTPServerRequest:(HTTPServerRequest *)request; +- (void)HTTPConnection:(HTTPConnection *)conn didReceiveRequest:(HTTPServerRequest *)mess; + +@end + +@implementation OKTRedirectHTTPHandlerTests + +- (void)testIsOptionsHTTPServerRequest { + OKTRedirectHTTPHandler *handler = [OKTRedirectHTTPHandler new]; + CFStringRef url = CFSTR("http://www.okta.com"); + CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL); + + CFStringRef requestMethod = CFSTR("GET"); + CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL, kCFHTTPVersion1_1); + HTTPConnection *connection = [[HTTPConnection alloc] initWithPeerAddress:nil inputStream:nil outputStream:nil forServer:nil]; + HTTPServerRequest *request = [[HTTPServerRequest alloc] initWithRequest:myRequest connection:connection]; + BOOL isOptionsRequest = [handler isOptionsHTTPServerRequest: request]; + XCTAssertFalse(isOptionsRequest); + + requestMethod = CFSTR("OPTIONS"); + myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL, kCFHTTPVersion1_1); + request = [[HTTPServerRequest alloc] initWithRequest:myRequest connection:connection]; + isOptionsRequest = [handler isOptionsHTTPServerRequest: request]; + XCTAssertTrue(isOptionsRequest); + + requestMethod = CFSTR("options"); + myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL, kCFHTTPVersion1_1); + request = [[HTTPServerRequest alloc] initWithRequest:myRequest connection:connection]; + isOptionsRequest = [handler isOptionsHTTPServerRequest: request]; + XCTAssertTrue(isOptionsRequest); + + isOptionsRequest = [handler isOptionsHTTPServerRequest: nil]; + XCTAssertFalse(isOptionsRequest); +} + +- (void)testDidReceiveRequestDelegate_NoOptionsHeader { + OKTRedirectHTTPHandler *handler = [OKTRedirectHTTPHandler new]; + handler.currentAuthorizationFlow = (id)self; + CFStringRef url = CFSTR("http://www.okta.com"); + CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL); + CFStringRef requestMethod = CFSTR("GET"); + CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL, kCFHTTPVersion1_1); + HTTPConnection *connection = [[HTTPConnection alloc] initWithPeerAddress:nil inputStream:nil outputStream:nil forServer:nil]; + HTTPServerRequest *request = [[HTTPServerRequest alloc] initWithRequest:myRequest connection:connection]; + + self.resumeExternalUserAgentFlowWithURLCalled = NO; + [handler HTTPConnection:connection didReceiveRequest:request]; + XCTAssertTrue(self.resumeExternalUserAgentFlowWithURLCalled); + + myURL = nil; + requestMethod = CFSTR("GET"); + myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL, kCFHTTPVersion1_1); + request = [[HTTPServerRequest alloc] initWithRequest:myRequest connection:connection]; + self.resumeExternalUserAgentFlowWithURLCalled = NO; + [handler HTTPConnection:connection didReceiveRequest:request]; + XCTAssertFalse(self.resumeExternalUserAgentFlowWithURLCalled); +} + +- (void)testDidReceiveRequestDelegate_WithOptionsHeader { + OKTRedirectHTTPHandler *handler = [OKTRedirectHTTPHandler new]; + handler.currentAuthorizationFlow = (id)self; + CFStringRef url = CFSTR("http://www.okta.com"); + CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL); + CFStringRef requestMethod = CFSTR("OPTIONS"); + CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL, kCFHTTPVersion1_1); + HTTPConnection *connection = [[HTTPConnection alloc] initWithPeerAddress:nil inputStream:nil outputStream:nil forServer:nil]; + HTTPServerRequest *request = [[HTTPServerRequest alloc] initWithRequest:myRequest connection:connection]; + CFStringRef privateNetworkHeader = CFSTR("Access-Control-Request-Private-Network"); + CFStringRef privateNetworkValue = CFSTR("true"); + CFHTTPMessageSetHeaderFieldValue(myRequest, privateNetworkHeader, privateNetworkValue); + CFStringRef originHeader = CFSTR("Origin"); + CFStringRef originValue = CFSTR("http://www.okta.com"); + CFHTTPMessageSetHeaderFieldValue(myRequest, originHeader, originValue); + + self.resumeExternalUserAgentFlowWithURLCalled = NO; + [handler HTTPConnection:connection didReceiveRequest:request]; + XCTAssertFalse(self.resumeExternalUserAgentFlowWithURLCalled); + + CFHTTPMessageRef response = request.response; + + originHeader = CFHTTPMessageCopyHeaderFieldValue(response, (__bridge CFStringRef)@"Access-Control-Allow-Origin"); + BOOL originHeaderExists = CFStringCompare(originHeader, originValue, kCFCompareCaseInsensitive) == kCFCompareEqualTo; + XCTAssertTrue(originHeaderExists); + + CFStringRef allowCredentialsHeader = CFHTTPMessageCopyHeaderFieldValue(response, (__bridge CFStringRef)@"Access-Control-Allow-Credentials"); + BOOL allowCredentialsHeaderExists = CFStringCompare(allowCredentialsHeader, (__bridge CFStringRef)@"true", kCFCompareCaseInsensitive) == kCFCompareEqualTo; + XCTAssertTrue(allowCredentialsHeaderExists); + + CFStringRef allowPrivateNetworkHeader = CFHTTPMessageCopyHeaderFieldValue(response, (__bridge CFStringRef)@"Access-Control-Allow-Private-Network"); + BOOL allowPrivateNetworkHeaderExists = CFStringCompare(allowPrivateNetworkHeader, (__bridge CFStringRef)@"true", kCFCompareCaseInsensitive) == kCFCompareEqualTo; + XCTAssertTrue(allowPrivateNetworkHeaderExists); + + CFStringRef contentLengthHeader = CFHTTPMessageCopyHeaderFieldValue(response, (__bridge CFStringRef)@"Content-Length"); + BOOL contentLengthHeaderExists = CFStringCompare(contentLengthHeader, (__bridge CFStringRef)@"0", kCFCompareCaseInsensitive) == kCFCompareEqualTo; + XCTAssertTrue(contentLengthHeaderExists); +} + +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { + self.resumeExternalUserAgentFlowWithURLCalled = YES; + return true; +} + +- (void)failExternalUserAgentFlowWithError:(NSError *)error { +} + +@end diff --git a/okta-oidc.xcodeproj/project.pbxproj b/okta-oidc.xcodeproj/project.pbxproj index 394f7e028c1..3e4dee4a989 100644 --- a/okta-oidc.xcodeproj/project.pbxproj +++ b/okta-oidc.xcodeproj/project.pbxproj @@ -297,6 +297,7 @@ E2FB61312536779800D26EDC /* OKTAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2FB612E2536779800D26EDC /* OKTAuthorizationRequest.m */; }; E2FB61322536779800D26EDC /* OKTAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2FB612E2536779800D26EDC /* OKTAuthorizationRequest.m */; }; E2FB61422536785200D26EDC /* OKTAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = E2FB612D2536779800D26EDC /* OKTAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F05AE8372C5874850052CB99 /* OKTRedirectHTTPHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F05AE8362C5874850052CB99 /* OKTRedirectHTTPHandlerTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -552,6 +553,7 @@ E2F6026525104F3000492BEC /* OktaOIDAuthStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OktaOIDAuthStateTests.swift; sourceTree = ""; }; E2FB612D2536779800D26EDC /* OKTAuthorizationRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OKTAuthorizationRequest.h; path = include/OKTAuthorizationRequest.h; sourceTree = ""; }; E2FB612E2536779800D26EDC /* OKTAuthorizationRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OKTAuthorizationRequest.m; sourceTree = ""; }; + F05AE8362C5874850052CB99 /* OKTRedirectHTTPHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OKTRedirectHTTPHandlerTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -723,6 +725,7 @@ A17E39F62358FA3000837873 /* OKTURLSessionProviderTests.m */, 92C1DF5E27A459BC003773F5 /* OKTDefaultTokenValidatorTests.h */, 92C1DF5F27A459BC003773F5 /* OKTDefaultTokenValidatorTests.m */, + F05AE8362C5874850052CB99 /* OKTRedirectHTTPHandlerTests.m */, ); path = AppAuthTests; sourceTree = ""; @@ -1578,6 +1581,7 @@ A167889C2432CDD800D1651D /* OKTRedirectHTTPHandlerMock.swift in Sources */, A167889E2433C7B500D1651D /* OktaRedirectServerConfigurationTests.swift in Sources */, 92C1DF6127A47347003773F5 /* OKTDefaultTokenValidatorTests.m in Sources */, + F05AE8372C5874850052CB99 /* OKTRedirectHTTPHandlerTests.m in Sources */, A16788C22436B1DF00D1651D /* OKTExternalUserAgentSessionMock.swift in Sources */, 9601C356256DD14900C084F5 /* OIDExternalUserAgentNoSsoTests.swift in Sources */, A16788AE243567A400D1651D /* OktaOidcBrowserTaskMacMock.swift in Sources */, @@ -1796,7 +1800,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.11.5; + MARKETING_VERSION = 3.11.6; PRODUCT_BUNDLE_IDENTIFIER = "com.okta.okta-oidc"; PRODUCT_NAME = OktaOidc; SKIP_INSTALL = YES; @@ -1826,7 +1830,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.11.5; + MARKETING_VERSION = 3.11.6; PRODUCT_BUNDLE_IDENTIFIER = "com.okta.okta-oidc"; PRODUCT_NAME = OktaOidc; SKIP_INSTALL = YES; @@ -1974,7 +1978,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 3.11.5; + MARKETING_VERSION = 3.11.6; PRODUCT_BUNDLE_IDENTIFIER = "com.okta.okta-oidc"; PRODUCT_NAME = OktaOidc; SDKROOT = macosx; @@ -2001,7 +2005,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 3.11.5; + MARKETING_VERSION = 3.11.6; PRODUCT_BUNDLE_IDENTIFIER = "com.okta.okta-oidc"; PRODUCT_NAME = OktaOidc; SDKROOT = macosx;