Skip to content

Commit

Permalink
Merge pull request #26 from AckeeCZ/empty_strings
Browse files Browse the repository at this point in the history
Add support for empty strings
  • Loading branch information
LukasHromadnik authored Mar 8, 2022
2 parents c9648c3 + 5015265 commit 16a499e
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 96 deletions.
61 changes: 8 additions & 53 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,21 @@
"object": {
"pins": [
{
"package": "Cryptor",
"repositoryURL": "https://github.com/IBM-Swift/BlueCryptor.git",
"package": "jwt-kit",
"repositoryURL": "https://github.com/vapor/jwt-kit",
"state": {
"branch": null,
"revision": "12d2bf3ec7207ec3cd004b9582f69ef5fae1da3b",
"version": "1.0.32"
"revision": "1822bb0abf0a31a4b5078ec19061c548835253b5",
"version": "4.3.0"
}
},
{
"package": "CryptorECC",
"repositoryURL": "https://github.com/IBM-Swift/BlueECC.git",
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto.git",
"state": {
"branch": null,
"revision": "73f362cb0d9c5f1fd0089240d7b293cd2bff18db",
"version": "1.2.5"
}
},
{
"package": "CryptorRSA",
"repositoryURL": "https://github.com/IBM-Swift/BlueRSA.git",
"state": {
"branch": null,
"revision": "e906f5e93426bb43ad7ac37cc20008d8fae608d9",
"version": "1.0.34"
}
},
{
"package": "KituraContracts",
"repositoryURL": "https://github.com/IBM-Swift/KituraContracts.git",
"state": {
"branch": null,
"revision": "a30e2fb79e926672776a05ec6b919c239870a221",
"version": "1.2.1"
}
},
{
"package": "LoggerAPI",
"repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git",
"state": {
"branch": null,
"revision": "3357dd9526cdf9436fa63bb792b669e6efdc43da",
"version": "1.9.0"
}
},
{
"package": "SwiftJWT",
"repositoryURL": "https://github.com/IBM-Swift/Swift-JWT",
"state": {
"branch": null,
"revision": "0d435423d12e61c0d14adb6d04396c08a6a650f1",
"version": "3.6.1"
}
},
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "74d7b91ceebc85daf387ebb206003f78813f71aa",
"version": "1.2.0"
"revision": "a8911e0fadc25aef1071d582355bd1037a176060",
"version": "2.0.4"
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ let package = Package(
targets: ["ACKLocalization"]),
],
dependencies: [
.package(url:"https://github.com/IBM-Swift/Swift-JWT", .upToNextMajor(from: "3.6.1")),
.package(url:"https://github.com/vapor/jwt-kit", .upToNextMajor(from: "4.3.0")),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "ACKLocalizationCore",
dependencies: ["SwiftJWT"]),
dependencies: ["JWTKit"]),
.target(
name: "ACKLocalization",
dependencies: ["ACKLocalizationCore"]),
Expand Down
11 changes: 5 additions & 6 deletions Sources/ACKLocalizationCore/ACKLocalization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,11 @@ public final class ACKLocalization {
// find index of current language
guard let langIndex = valueRange.firstIndex(columnName: sheetColName) else { return }

// try to get value for current language
if let value = rowValues[safe: langIndex].map ({ LocRow(key: key, value: $0) }) {
var langRows = result[langCode] ?? []
langRows.append(value)
result[langCode] = langRows
}
let value = LocRow(key: key, value: rowValues[safe: langIndex] ?? "")

var langRows = result[langCode] ?? []
langRows.append(value)
result[langCode] = langRows
}
}

Expand Down
30 changes: 22 additions & 8 deletions Sources/ACKLocalizationCore/Model/GoogleClaims.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,36 @@
//

import Foundation
import SwiftJWT
import JWTKit

/// Struct that is used for generating second part of JWT token
struct GoogleClaims: Claims {
struct GoogleClaims: JWTPayload {
enum Scope: String, Codable {
case readOnly = "https://www.googleapis.com/auth/spreadsheets.readonly"
}

/// Service account email
let iss: String

/// Required scope
let scope = "https://www.googleapis.com/auth/spreadsheets.readonly"
let scope: Scope

/// Desired auth endpoint
let aud = "https://oauth2.googleapis.com/token"
let aud: URL

/// Date of expiration timestamp
let exp: Int

/// Issued at date timestamp
let iat: Int
}

extension GoogleClaims {
init(serviceAccount: ServiceAccount, scope: Scope, exp: Int, iat: Int) {
self.init(iss: serviceAccount.clientEmail, scope: scope, aud: serviceAccount.tokenURL, exp: exp, iat: iat)
}

func verify(using signer: JWTSigner) throws {
try ExpirationClaim(value: Date(timeIntervalSince1970: TimeInterval(exp))).verifyNotExpired()
}
}
2 changes: 1 addition & 1 deletion Sources/ACKLocalizationCore/Model/LocRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// Struct representing single `Localizable.strings` row
public struct LocRow {
public struct LocRow: Equatable {
/// Key of current row
public let key: String

Expand Down
13 changes: 5 additions & 8 deletions Sources/ACKLocalizationCore/Model/ServiceAccount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,15 @@ public struct ServiceAccount: Decodable {
enum CodingKeys: String, CodingKey {
case clientEmail = "client_email"
case privateKey = "private_key"
case tokenURL = "token_uri"
}

/// Email associated with the service account
public let clientEmail: String
let clientEmail: String

/// Private key used to generate JWT token
public let privateKey: String
let privateKey: String

// MARK: - Initializers

public init(clientEmail: String, privateKey: String) {
self.clientEmail = clientEmail
self.privateKey = privateKey
}
/// URL for fetching OAuth token
let tokenURL: URL
}
6 changes: 1 addition & 5 deletions Sources/ACKLocalizationCore/Model/ValueRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import Foundation

/// Struct holding content of a single sheet
public struct ValueRange: Decodable {
/// Represented range in sheet
public let range: String

/// String values in sheet
public let values: [[String]]

Expand All @@ -22,8 +19,7 @@ public struct ValueRange: Decodable {
values.first?.firstIndex(of: columnName)
}

public init(range: String, values: [[String]]) {
self.range = range
public init(values: [[String]]) {
self.values = values
}
}
36 changes: 23 additions & 13 deletions Sources/ACKLocalizationCore/Services/AuthAPIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Combine
import Foundation
import SwiftJWT
import JWTKit

/// Protocol wrapping a service that fetches an access token from further communication
public protocol AuthAPIServicing {
Expand All @@ -29,10 +29,12 @@ public struct AuthAPIService: AuthAPIServicing {

/// Fetch access token for given `serviceAccount`
public func fetchAccessToken(serviceAccount: ServiceAccount) -> AnyPublisher<AccessToken, RequestError> {
let jwt = self.jwt(for: serviceAccount)
let url = URL(string: "https://oauth2.googleapis.com/token")!
let requestData = AccessTokenRequest(assertion: jwt)
var request = URLRequest(url: url)
let jwt = try? self.jwt(
for: serviceAccount,
claims: claims(serviceAccount: serviceAccount, validFor: 60)
)
let requestData = AccessTokenRequest(assertion: jwt ?? "")
var request = URLRequest(url: serviceAccount.tokenURL)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONEncoder().encode(requestData)
Expand All @@ -47,12 +49,20 @@ public struct AuthAPIService: AuthAPIServicing {
// MARK: - Private helpers

/// Create JWT token that will be sent to retrieve access token
private func jwt(for serviceAccount: ServiceAccount) -> String {
let header = Header(typ: "JWT")
let now = Int(Date().timeIntervalSince1970)
let claims = GoogleClaims(iss: serviceAccount.clientEmail, exp: now + 60, iat: now)
var jwt = JWT(header: header, claims: claims)
let signer = JWTSigner.rs256(privateKey: serviceAccount.privateKey.data(using: .utf8)!)
return (try? jwt.sign(using: signer)) ?? ""
}
private func jwt(for serviceAccount: ServiceAccount, claims: GoogleClaims) throws -> String {
let signers = JWTSigners()
try signers.use(.rs256(key: .private(pem: serviceAccount.privateKey)))
return try signers.sign(claims)
}

private func claims(serviceAccount sa: ServiceAccount, validFor interval: TimeInterval) -> GoogleClaims {
let now = Int(Date().timeIntervalSince1970)

return .init(
serviceAccount: sa,
scope: .readOnly,
exp: now + Int(interval),
iat: now
)
}
}
24 changes: 24 additions & 0 deletions Tests/ACKLocalizationCoreTests/ACKLocalizationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import ACKLocalizationCore
import XCTest

final class ACKLocalizationTests: XCTestCase {
func test_transform_emptyRow() throws {
let localization = ACKLocalization()
let mappedValues = try localization.transformValues(
.init(
values: [
["keys", "cs"],
["key"]
]
),
with: ["cs": "cs"],
keyColumnName: "keys"
)

XCTAssertEqual(Array(mappedValues.keys), ["cs"])
XCTAssertEqual(
mappedValues.values.flatMap { $0 },
[LocRow(key: "key", value: "")]
)
}
}

0 comments on commit 16a499e

Please sign in to comment.