I am trying to write some code that lets me both validate that a string is in fact a valid web address to reach a remote server && be able to unwrap it safely into a url for usage.
From what gathered from various posts and Apple's source code:
URLComponents is a structure designed to parse URLs based on RFC 3986 and to construct URLs from their constituent parts.
and based on w3 schools:
A URL is a valid URL if at least one of the following conditions holds:
The URL is a valid URI reference [RFC3986]....
Would this code suffice to detect addresses that reach remote servers on the world wide web?
import Foundation
extension String {
/// Returns `nil` if a valid web address cannot be initialized from self
var url: URL? {
guard
let urlComponents = URLComponents(string: self),
let scheme = urlComponents.scheme,
isWebServerUrl(scheme: scheme),
let url = urlComponents.url
else {
return nil
}
return url
}
/// A web address normally starts with http:// or https:// (regular http protocol or secure http protocol).
private func isWebServerUrl(scheme: String) -> Bool {
(scheme == WebSchemes.http.rawValue || scheme == WebSchemes.https.rawValue)
}
}
Can you provide some feedback on this approach and let me know if there are any optimizations that can be made? or if it's incorrect?
Any and all comments are appreciated.
You could go even simpler and do
import Foundation
extension String {
/// Returns `nil` if a valid web address cannot be initialized from self
var url: URL? {
return URL(string: self)
}
/// A web address normally starts with http:// or https:// (regular http protocol or secure http protocol).
var isWebURL: Bool {
get {
guard let url = self.url else { return false }
return url.scheme == "http" || url.scheme == "https"
}
}
}
Explanation
Initializing a URL using a string will return nil if the string isn't a valid url, so you can get the url by just initing a URL object. Additionally, checking the scheme is super easy because URL has a property scheme that we can check against the required parameters.
Related
I will get straight to the point. Please excuse my english
So there is a website (we can call it X) which offers a service to query and fetch documents etc after logging in with your username / password
I am trying to figure out a way to, basically, provide the same service except natively through the app. The user of the app will still have to enter their username and password and then I make a request to the website and "log" in and then provide the same interface the website does after logging in but the app will be able to save the login information and be able to have some other features that would be beneficial wrt to the documents that it then fetches.
The website does not offer an api (atleast that I know of) I am struggling to figure out how to send the URL request with the username and password.
First I read about using ASAuthenticationServices but that led me to reading that you need the callback url which did not work for me (1. because I can't setup the callback url through the API and 2. because my custom app callback url did not fire)
Then I tried to use a WKWebView to embed a browser and a try to catch the details after the user logged in but still no success.
I have also read that JWT might be the solution considering that it is only a single server authentication needed
This is my code so far, I have stripped it down to basically just show the request I'm making
If anyone could shed some light as to how to perform a simple web login (to basically embed the web service and be able to use it as an app) I would really appreciate it.
Thanks
So this is where I'm at now. I'm trying to form a URLRequest and just checking whether the signin response works, should I be trying to implement a callback in a custom WKWebView ? I'm more kind of asking as to what method I should be using. Should I be researching more into JWT ? or should I be looking at using custom WKWebViews and trying to catch the callback and save the credentials etc through that or do I need to just deconstruct and send custom URLRequests in order to authenticate? thank you
import Combine
import Foundation
final class SignInDummy: ObservableObject {
#Published var username = ""
#Published var password = ""
#Published var hasError = false
#Published var isSigningIn = false
var canSignIn: Bool {
!username.isEmpty && !password.isEmpty
}
func signIn() {
guard !username.isEmpty && !password.isEmpty else {
return
}
let url = URL(string: "https:// URL to website (copy and pasted url of the login page)")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let authData = (username + ":" + password).data(using: .utf8)!.base64EncodedString()
request.addValue("Basic \(authData)", forHTTPHeaderField: "Authorization")
isSigningIn = true
URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
DispatchQueue.main.async {
if error != nil || (response as! HTTPURLResponse).statusCode != 200 {
self?.hasError = true
} else if let data = data {
do {
let signInResponse = try JSONDecoder().decode(SignInResponse.self, from: data)
print(signInResponse)
// TODO: Cache Access Token in Keychain
} catch {
print("Unable to Decode Response \(error)")
print("\(response?.mimeType)")
print("\(response.debugDescription)")
}
}
self?.isSigningIn = false
}
}.resume()
}
fileprivate struct SignInResponse: Decodable {
// MARK: - Properties
let accessToken: String
}
}
I'm currently using flutter to pass a String of a valid file path to Swift in order to gain access to a security scoped resource (this part might not be relevant)
So I have a function that accepts a String and goes like this:
public func requestAccessToFile(filePath: String) -> Bool {
let fileUrl = URL(fileURLWithPath: filePath)
return fileUrl.startAccessingSecurityScopedResource()
}
I know that startAccessingSecurityScopedResource not always returns true but in this case, it should, since if I try to access the file without this returning true I get permissions error.
A bit more context: If I try to call that startAccessingSecurityScopedResource as soon as I get the URL from the file picker, it does succeed, but if I do it with the function it fails (notice that the function is called with a String and I'm passing a path without the file:// protocol. eg. "/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/Documents/afile.pdf"
So my guess is that the URL created by the file picker is somehow different that the one I'm creating with the string path. But not sure.
Thanks for your help in advance.
UIDocumentPickerViewController provides a security-scoped URL to access a resource and it is not possible to make the same from a string path:
If you need a security-scoped URL’s path as a string value (as provided by the path method), such as to provide to an API that requires a string value, obtain the path from the URL as needed. Note, however, that a string-based path obtained from a security-scoped URL does not have security scope and you cannot use that string to obtain access to a security-scoped resource. https://developer.apple.com/documentation/foundation/nsurl
If you need to save or share a location of secured resource in your code you should use bookmarks:
// Get bookmark data from the provided URL
let bookmarkData = try? pickedURL.bookmarkData()
if let data = bookmarkData {
// Save data
...
}
...
// Access to an external document by the bookmark data
if let data = bookmarkData {
var stale = false
if let url = try? URL(resolvingBookmarkData: data, bookmarkDataIsStale: &stale),
stale == false,
url.startAccessingSecurityScopedResource()
{
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { readURL in
if let data = try? Data(contentsOf: readURL) {
...
}
}
url.stopAccessingSecurityScopedResource()
}
}
I am trying to get all the information for an URLRequest in order to save it in a log. The problem comes when I try to do String(reflecting: request). This method only gives my the final URL but not all the the other information such us body, httpMethod, encoding, etc.
However, if I do po request, I can see all the information of the request.
Any hint?
Thanks
You can use the .description and .debugDescription of an object to outpout a description. These properties rely on the CustomStringConvertible and CustomDebugStringConvertible protocols respectively.
To change what is printed out you can define/redefine the conformance to the protocol. As a trivial example for URLRequest:
extension URLRequest: CustomDebugStringConvertible {
var debugDescription: String {
"""
URL Request for \(self)
full url \(self.url)
header fields \(self.allHTTPHeaderFields)
"""
}
}
let url = URL(string: "https://somedomain.com")
var request = URLRequest(url: url!)
request.addValue("ghuaidbwjkbdwd", forHTTPHeaderField: "Auhentication")
print(request.debugDescription)
will output
URL Request for https://somedomain.com
full url Optional(https://somedomain.com)
header fields Optional(["Auhentication": "ghuaidbwjkbdwd"])
Obviously you'd probably want to do something a bit more useful with the output but this should give you the tools.
When doing this for an object with a pre-defined conformance (eg. URLRequest, where it is defined in Foundation) you will get a warning about the previous implementation.
In this scenario, as the object already conforms to the description protocols, you can leave out re-conforming to it and just override the property. So in the above example you should actually use
extension URLRequest {
var debugDescription: String {
If the object you are working with is a class, you will need to mark this as an override.
If you want to get all the properties of the object you could use a Mirror. You could define this directly on the object with an extension, but if it is something you are likely to use in more than one place maybe implement it via a protocol with a default implementation:
protocol Reflectable {
func reflect()
}
extension Reflectable {
func reflect() {
let mirror = Mirror(reflecting: self)
print("\(Self.self) for: \(self)")
for item in mirror.children {
print("\(String(describing: item.label)): \(item.value)")
}
}
}
You can then subscribe URLRequest to this and either access the .reflect() method directly or build it into either CustomStringConvertible or CustomDebugStringConvertible
extension URLRequest: Reflectable {}
request.reflect()
providing an output of:
URLRequest for: https://somedomain.com
Optional("url"): Optional(https://somedomain.com)
Optional("cachePolicy"): 0
Optional("timeoutInterval"): 60.0
Optional("mainDocumentURL"): nil
Optional("networkServiceType"): NSURLRequestNetworkServiceType
Optional("allowsCellularAccess"): true
Optional("httpMethod"): Optional("GET")
Optional("allHTTPHeaderFields"): Optional(["Auhentication": "ghuaidbwjkbdwd"])
Optional("httpBody"): nil
Optional("httpBodyStream"): nil
Optional("httpShouldHandleCookies"): true
Optional("httpShouldUsePipelining"): false
I'm currently using flutter to pass a String of a valid file path to Swift in order to gain access to a security scoped resource (this part might not be relevant)
So I have a function that accepts a String and goes like this:
public func requestAccessToFile(filePath: String) -> Bool {
let fileUrl = URL(fileURLWithPath: filePath)
return fileUrl.startAccessingSecurityScopedResource()
}
I know that startAccessingSecurityScopedResource not always returns true but in this case, it should, since if I try to access the file without this returning true I get permissions error.
A bit more context: If I try to call that startAccessingSecurityScopedResource as soon as I get the URL from the file picker, it does succeed, but if I do it with the function it fails (notice that the function is called with a String and I'm passing a path without the file:// protocol. eg. "/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/Documents/afile.pdf"
So my guess is that the URL created by the file picker is somehow different that the one I'm creating with the string path. But not sure.
Thanks for your help in advance.
UIDocumentPickerViewController provides a security-scoped URL to access a resource and it is not possible to make the same from a string path:
If you need a security-scoped URL’s path as a string value (as provided by the path method), such as to provide to an API that requires a string value, obtain the path from the URL as needed. Note, however, that a string-based path obtained from a security-scoped URL does not have security scope and you cannot use that string to obtain access to a security-scoped resource. https://developer.apple.com/documentation/foundation/nsurl
If you need to save or share a location of secured resource in your code you should use bookmarks:
// Get bookmark data from the provided URL
let bookmarkData = try? pickedURL.bookmarkData()
if let data = bookmarkData {
// Save data
...
}
...
// Access to an external document by the bookmark data
if let data = bookmarkData {
var stale = false
if let url = try? URL(resolvingBookmarkData: data, bookmarkDataIsStale: &stale),
stale == false,
url.startAccessingSecurityScopedResource()
{
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { readURL in
if let data = try? Data(contentsOf: readURL) {
...
}
}
url.stopAccessingSecurityScopedResource()
}
}
I'm trying to obtain an auth code from Stripe's OAuth endpoint using ASWebAuthenticationSession - this event happens after the my Stripe redirect url gets called.
Unfortunately, the authSession's completion handler doesn't call back with a callbackURL. And I need this callbackURL to query the auth code. I've read different articles on this topic but I can't figure out why my implementation doesn't work they way I expect it to.
Here's my code:
class CreateStripeConnectAccountController: UIViewController {
var authSession: ASWebAuthenticationSession!
override func viewDidLoad() {
super.viewDidLoad()
configureAuthSession()
}
private func configureAuthSession() {
let urlString = Constants.URLs.stripeConnectOAuth // Sample URL
guard let url = URL(string: urlString) else { return }
let callbackScheme = "myapp:auth"
authSession = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackScheme, completionHandler: { (callbackURL, error) in
guard error == nil, let successURL = callbackURL else {
print("Nothing")
return
}
let oauthToken = NSURLComponents(string: (successURL.absoluteString))?.queryItems?.filter({$0.name == "code"}).first
print(successURL.absoluteString)
})
authSession.presentationContextProvider = self
authSession.start()
}
}
extension CreateStripeConnectAccountController: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
self.view.window ?? ASPresentationAnchor()
}
}
I believe the issue is that you are giving nil for callbackURLScheme. You need to give a URL scheme that redirects to your app:
See apple's authentication example: https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service
And here's apple's docs on how to create a custom URL scheme for your app: https://developer.apple.com/documentation/uikit/inter-process_communication/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app.
I know it's old, but anyway.
Make sure, that callbackScheme and scheme that is used in redirect_uri are the same.
Your callbackScheme myapp:auth is incorrect format.
The symbol : cannot be used in the scheme name of a URI.
See the following RFC definition of Scheme.
Scheme names consist of a sequence of characters beginning with a
letter and followed by any combination of letters, digits, plus
("+"), period ("."), or hyphen ("-").
https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
Thefore, revising the callbackURLscheme as myapp-auth or myapp.auth works well.
try setting your callbackScheme = "myapp" to receive callback
and from your server-side it should return "myapp://auth?token=1234"
Hope it helps.