-
Notifications
You must be signed in to change notification settings - Fork 12
/
CredentialsHTTPBasic.swift
144 lines (124 loc) · 6.03 KB
/
CredentialsHTTPBasic.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* Copyright IBM Corporation 2016
*
* 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 Kitura
import KituraNet
import Credentials
import Foundation
// MARK CredentialsHTTPBasic
/// Authenticate requests using HTTP Basic authentication.
/// See [RFC 7617](https://tools.ietf.org/html/rfc7617) for details.
public class CredentialsHTTPBasic : CredentialsPluginProtocol {
/// The name of the plugin.
public var name: String {
return "HTTPBasic"
}
/// An indication as to whether the plugin is redirecting or not.
public var redirecting: Bool {
return false
}
/// User profile cache.
public var usersCache: NSCache<NSString, BaseCacheElement>?
private var userProfileLoader: UserProfileLoader? = nil
private var verifyPassword: VerifyPassword? = nil
/// The authentication realm attribute.
public var realm: String
/// Initialize a `CredentialsHTTPBasic` instance.
///
/// - Parameter userProfileLoader: The callback for loading the user profile.
/// - Parameter realm: The realm attribute.
@available(*, deprecated: 2.0, message: "userProfileLoader has been deprecated from Basic Authentication because of security improvements. Please use verifyPassword.") public init (userProfileLoader: @escaping UserProfileLoader, realm: String?=nil) {
self.userProfileLoader = userProfileLoader
self.realm = realm ?? "Users"
}
/// Initialize a `CredentialsHTTPBasic` instance.
///
/// - Parameter verifyPassword: The callback for verifying the password of the user.
/// - Parameter realm: The realm attribute.
public init (verifyPassword: @escaping VerifyPassword, realm: String?=nil) {
self.verifyPassword = verifyPassword
self.realm = realm ?? "Users"
}
/// Authenticate incoming request using HTTP Basic authentication.
///
/// - Parameter request: The `RouterRequest` object used to get information
/// about the request.
/// - Parameter response: The `RouterResponse` object used to respond to the
/// request.
/// - Parameter options: The dictionary of plugin specific options.
/// - Parameter onSuccess: The closure to invoke in the case of successful authentication.
/// - Parameter onFailure: The closure to invoke in the case of an authentication failure.
/// - Parameter onPass: The closure to invoke when the plugin doesn't recognize the
/// authentication data in the request.
/// - Parameter inProgress: The closure to invoke to cause a redirect to the login page in the
/// case of redirecting authentication.
public func authenticate (request: RouterRequest, response: RouterResponse,
options: [String:Any], onSuccess: @escaping (UserProfile) -> Void,
onFailure: @escaping (HTTPStatusCode?, [String:String]?) -> Void,
onPass: @escaping (HTTPStatusCode?, [String:String]?) -> Void,
inProgress: @escaping () -> Void) {
var authorization : String
if let user = request.urlURL.user, let password = request.urlURL.password {
authorization = user + ":" + password
}
else {
let options = Data.Base64DecodingOptions(rawValue: 0)
guard let authorizationHeader = request.headers["Authorization"] else {
onPass(.unauthorized, ["WWW-Authenticate" : "Basic realm=\"" + self.realm + "\""])
return
}
let authorizationHeaderComponents = authorizationHeader.components(separatedBy: " ")
guard authorizationHeaderComponents.count == 2,
authorizationHeaderComponents[0] == "Basic",
let decodedData = Data(base64Encoded: authorizationHeaderComponents[1], options: options),
let userAuthorization = String(data: decodedData, encoding: .utf8) else {
onPass(.unauthorized, ["WWW-Authenticate" : "Basic realm=\"" + self.realm + "\""])
return
}
authorization = userAuthorization as String
}
let credentials = authorization.components(separatedBy: ":")
guard credentials.count >= 2 else {
onFailure(.badRequest, nil)
return
}
let userid = credentials[0]
let password = credentials[1]
if let userProfileLoader = self.userProfileLoader {
userProfileLoader(userid) { userProfile, storedPassword in
if let userProfile = userProfile, let storedPassword = storedPassword, storedPassword == password {
onSuccess(userProfile)
}
else {
onFailure(.unauthorized, ["WWW-Authenticate" : "Basic realm=\"" + self.realm + "\""])
}
}
}
else if let verifyPassword = self.verifyPassword {
verifyPassword(userid, password) { userProfile in
if let userProfile = userProfile {
onSuccess(userProfile)
}
else {
onFailure(.unauthorized, ["WWW-Authenticate" : "Basic realm=\"" + self.realm + "\""])
}
}
}
else {
// either verifyPassword or userProfileLoader must be valid
onFailure(.internalServerError, ["WWW-Authenticate" : "Internal server error"])
}
}
}