Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connection should optionally use client credentials #122

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
SwiftLint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: Swiftlint verification
uses: norio-nomura/[email protected]
with:
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
<!-- Add any information here about changes in master that have yet to be released -->

N/A
### Added
- Connection to Jamf Pro can now use client credentials with Jamf Pro v10.49+ ([Issue #120](https://github.com/jamf/PPPC-Utility/issues/120)) [@macblazer](https://github.com/macblazer).

### Changed
- PPPC Utility now requires macOS 11+ to run. It can still produce profiles usable on older versions of macOS.

## [1.5.0] - 2022-10-04

Expand Down
35 changes: 28 additions & 7 deletions PPPC Utility.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -36,16 +36,19 @@
6EC40A12214DF8FE00BE4F17 /* SecurityWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC40A11214DF8FE00BE4F17 /* SecurityWrapper.swift */; };
6EC40A14214DFB5800BE4F17 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC40A13214DFB5800BE4F17 /* Model.swift */; };
6EC40A16214ECF1E00BE4F17 /* SaveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC40A15214ECF1E00BE4F17 /* SaveViewController.swift */; };
6EC40A18214ECF2C00BE4F17 /* UploadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC40A17214ECF2C00BE4F17 /* UploadViewController.swift */; };
6EC40A1C214EF87800BE4F17 /* SigningIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC40A1B214EF87800BE4F17 /* SigningIdentity.swift */; };
71061E54246106C800822D35 /* LoadExecutableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71061E53246106C800822D35 /* LoadExecutableError.swift */; };
B5E09548250BCCFC00A40409 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E09547250BCCFC00A40409 /* Alert.swift */; };
C01BEDBA28636F57001B0B3B /* SemanticVersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01BEDB928636F57001B0B3B /* SemanticVersionTests.swift */; };
C03270BA28636330008B38E0 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03270B928636330008B38E0 /* SemanticVersion.swift */; };
C03270C028636397008B38E0 /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03270BE28636397008B38E0 /* Networking.swift */; };
C03270C128636397008B38E0 /* JamfProAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03270BF28636397008B38E0 /* JamfProAPIClient.swift */; };
C05844B82AD4512D00141353 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05844B72AD4512D00141353 /* Token.swift */; };
C05844BE2AD45F7900141353 /* UploadInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05844BD2AD45F7900141353 /* UploadInfoView.swift */; };
C07961E228749A36007B98A7 /* TokenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07961E128749A36007B98A7 /* TokenTests.swift */; };
C07961E428749A51007B98A7 /* NetworkAuthManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07961E328749A51007B98A7 /* NetworkAuthManagerTests.swift */; };
C07B1FB82AF596D80075E38B /* UploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07B1FB72AF596D80075E38B /* UploadManager.swift */; };
C0A2B5422B1A5D5C0007F510 /* JamfProAPIClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A2B5412B1A5D5C0007F510 /* JamfProAPIClientTests.swift */; };
C0A85DB5279873C600086283 /* TestTCCUnsignedProfile-allLower.mobileconfig in Resources */ = {isa = PBXBuildFile; fileRef = C0A85DB4279873C600086283 /* TestTCCUnsignedProfile-allLower.mobileconfig */; };
C0E0383F27A30C7100A23FA2 /* PPPCServiceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E0383D27A30C7100A23FA2 /* PPPCServiceInfo.swift */; };
C0E0384027A30C7100A23FA2 /* PPPCServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E0383E27A30C7100A23FA2 /* PPPCServicesManager.swift */; };
Expand Down Expand Up @@ -103,7 +106,6 @@
6EC40A11214DF8FE00BE4F17 /* SecurityWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityWrapper.swift; sourceTree = "<group>"; };
6EC40A13214DFB5800BE4F17 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
6EC40A15214ECF1E00BE4F17 /* SaveViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveViewController.swift; sourceTree = "<group>"; };
6EC40A17214ECF2C00BE4F17 /* UploadViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadViewController.swift; sourceTree = "<group>"; };
6EC40A1B214EF87800BE4F17 /* SigningIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigningIdentity.swift; sourceTree = "<group>"; };
71061E53246106C800822D35 /* LoadExecutableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadExecutableError.swift; sourceTree = "<group>"; };
97227C6726248CD7000F26C1 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
Expand All @@ -112,8 +114,12 @@
C03270B928636330008B38E0 /* SemanticVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SemanticVersion.swift; sourceTree = "<group>"; };
C03270BE28636397008B38E0 /* Networking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
C03270BF28636397008B38E0 /* JamfProAPIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JamfProAPIClient.swift; sourceTree = "<group>"; };
C05844B72AD4512D00141353 /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
C05844BD2AD45F7900141353 /* UploadInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadInfoView.swift; sourceTree = "<group>"; };
C07961E128749A36007B98A7 /* TokenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenTests.swift; sourceTree = "<group>"; };
C07961E328749A51007B98A7 /* NetworkAuthManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkAuthManagerTests.swift; sourceTree = "<group>"; };
C07B1FB72AF596D80075E38B /* UploadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadManager.swift; sourceTree = "<group>"; };
C0A2B5412B1A5D5C0007F510 /* JamfProAPIClientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JamfProAPIClientTests.swift; sourceTree = "<group>"; };
C0A85DB4279873C600086283 /* TestTCCUnsignedProfile-allLower.mobileconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "TestTCCUnsignedProfile-allLower.mobileconfig"; sourceTree = "<group>"; };
C0E0383D27A30C7100A23FA2 /* PPPCServiceInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PPPCServiceInfo.swift; sourceTree = "<group>"; };
C0E0383E27A30C7100A23FA2 /* PPPCServicesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PPPCServicesManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -257,6 +263,7 @@
6EC40A11214DF8FE00BE4F17 /* SecurityWrapper.swift */,
6E651CC623143969001CC974 /* Views */,
6EC40A1E214EF89600BE4F17 /* View Controllers */,
C05844BC2AD45F7900141353 /* SwiftUI */,
6EC40A1D214EF87E00BE4F17 /* Model */,
C03270BD28636397008B38E0 /* Networking */,
5F95AE0B23158AB5002E0A22 /* TCCProfileImporter */,
Expand Down Expand Up @@ -298,7 +305,6 @@
children = (
6EC409F2214D8FFA00BE4F17 /* TCCProfileViewController.swift */,
6EC40A15214ECF1E00BE4F17 /* SaveViewController.swift */,
6EC40A17214ECF2C00BE4F17 /* UploadViewController.swift */,
6E6216F8215321CE0043DF18 /* OpenViewController.swift */,
);
path = "View Controllers";
Expand All @@ -309,16 +315,27 @@
children = (
C0EE9A802863BE2B00738B6B /* NetworkAuthManager.swift */,
C03270BE28636397008B38E0 /* Networking.swift */,
C07B1FB72AF596D80075E38B /* UploadManager.swift */,
C03270BF28636397008B38E0 /* JamfProAPIClient.swift */,
C0EE9A7E2863BDE300738B6B /* JamfProAPITypes.swift */,
C05844B72AD4512D00141353 /* Token.swift */,
C0EE9A822863BEEB00738B6B /* URLSessionAsyncCompatibility.swift */,
);
path = Networking;
sourceTree = "<group>";
};
C05844BC2AD45F7900141353 /* SwiftUI */ = {
isa = PBXGroup;
children = (
C05844BD2AD45F7900141353 /* UploadInfoView.swift */,
);
path = SwiftUI;
sourceTree = "<group>";
};
C07961E028749A36007B98A7 /* NetworkingTests */ = {
isa = PBXGroup;
children = (
C0A2B5412B1A5D5C0007F510 /* JamfProAPIClientTests.swift */,
C07961E328749A51007B98A7 /* NetworkAuthManagerTests.swift */,
C07961E128749A36007B98A7 /* TokenTests.swift */,
);
Expand Down Expand Up @@ -441,6 +458,7 @@
/* Begin PBXShellScriptBuildPhase section */
49DB95D624991AA800F433CA /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand All @@ -466,6 +484,7 @@
files = (
C07961E228749A36007B98A7 /* TokenTests.swift in Sources */,
34DED4D423FDCAFD00C53FB9 /* SwiftyCMSDecoder.swift in Sources */,
C0A2B5422B1A5D5C0007F510 /* JamfProAPIClientTests.swift in Sources */,
C07961E428749A51007B98A7 /* NetworkAuthManagerTests.swift in Sources */,
5F95AE262315A7CB002E0A22 /* TCCProfileImporterTests.swift in Sources */,
C01BEDBA28636F57001B0B3B /* SemanticVersionTests.swift in Sources */,
Expand All @@ -492,20 +511,22 @@
C0E0383F27A30C7100A23FA2 /* PPPCServiceInfo.swift in Sources */,
C0EE9A7F2863BDE300738B6B /* JamfProAPITypes.swift in Sources */,
6EC40A1C214EF87800BE4F17 /* SigningIdentity.swift in Sources */,
6EC40A18214ECF2C00BE4F17 /* UploadViewController.swift in Sources */,
C03270C128636397008B38E0 /* JamfProAPIClient.swift in Sources */,
C0E0384027A30C7100A23FA2 /* PPPCServicesManager.swift in Sources */,
C07B1FB82AF596D80075E38B /* UploadManager.swift in Sources */,
6E651CCA231439CE001CC974 /* InfoButton.swift in Sources */,
6EC409F5214D95D200BE4F17 /* TCCProfile.swift in Sources */,
6EC40A12214DF8FE00BE4F17 /* SecurityWrapper.swift in Sources */,
6EC409DE214D65BC00BE4F17 /* AppDelegate.swift in Sources */,
345B01D623FDBF55008838B6 /* TCCProfileExtensions.swift in Sources */,
6EC409F3214D8FFA00BE4F17 /* TCCProfileViewController.swift in Sources */,
6E6216F9215321CE0043DF18 /* OpenViewController.swift in Sources */,
C05844B82AD4512D00141353 /* Token.swift in Sources */,
6EC40A10214DE3B200BE4F17 /* Executable.swift in Sources */,
C0EE9A832863BEEB00738B6B /* URLSessionAsyncCompatibility.swift in Sources */,
C0EE9A812863BE2B00738B6B /* NetworkAuthManager.swift in Sources */,
6EC40A16214ECF1E00BE4F17 /* SaveViewController.swift in Sources */,
C05844BE2AD45F7900141353 /* UploadInfoView.swift in Sources */,
6EB45830214FFCCB00BE5749 /* AppleEventRule.swift in Sources */,
5F90EBDF2319970000738D09 /* TCCProfileImportError.swift in Sources */,
C03270BA28636330008B38E0 /* SemanticVersion.swift in Sources */,
Expand Down Expand Up @@ -634,7 +655,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IBSC_NOTICES = NO;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -690,7 +711,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IBSC_NOTICES = NO;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
Expand Down
1 change: 0 additions & 1 deletion PPPC UtilityTests/ModelTests/ModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ class ModelTests: XCTestCase {
}
}

// swiftlint:disable:next function_body_length
func testExportProfileWithAppleEventsAndLegacyAllowed() {
// given
let exe1 = Executable(identifier: "one", codeRequirement: "oneReq")
Expand Down
27 changes: 27 additions & 0 deletions PPPC UtilityTests/NetworkingTests/JamfProAPIClientTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// JamfProAPIClientTests.swift
// PPPC UtilityTests
//
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 Jamf Software

import Foundation
import XCTest

@testable import PPPC_Utility

class JamfProAPIClientTests: XCTestCase {
func testOAuthTokenRequest() throws {
// given
let authManager = NetworkAuthManager(username: "", password: "")
let apiClient = JamfProAPIClient(serverUrlString: "https://something", tokenManager: authManager)

// when
let request = try apiClient.oauthTokenRequest(clientId: "mine&yours", clientSecret: "foo bar")

// then
let body = try XCTUnwrap(request.httpBody)
let bodyString = String(data: body, encoding: .utf8)
XCTAssertEqual(bodyString, "grant_type=client_credentials&client_id=mine%26yours&client_secret=foo%20bar")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ class MockNetworking: Networking {
super.init(serverUrlString: "https://example.com", tokenManager: tokenManager)
}

override func getBearerToken() async throws -> Token {
override func getBearerToken(authInfo: AuthenticationInfo) async throws -> Token {
if let error = errorToThrow {
throw error
}

return Token(value: "xyz", expireTime: "2950-06-22T22:05:58.81Z")
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let expiration = try XCTUnwrap(formatter.date(from: "2950-06-22T22:05:58.81Z"))

return Token(value: "xyz", expiresAt: expiration)
}
}

Expand Down
78 changes: 74 additions & 4 deletions PPPC UtilityTests/NetworkingTests/TokenTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ import XCTest
@testable import PPPC_Utility

class TokenTests: XCTestCase {
func testPastIsNotValid() {
func testPastIsNotValid() throws {
// given
let token = Token(value: "abc", expireTime: "2021-06-22T22:05:58.81Z")
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let expiration = try XCTUnwrap(formatter.date(from: "2021-06-22T22:05:58.81Z"))
let token = Token(value: "abc", expiresAt: expiration)

// when
let valid = token.isValid
Expand All @@ -42,14 +45,81 @@ class TokenTests: XCTestCase {
XCTAssertFalse(valid)
}

func testFutureIsValid() {
func testFutureIsValid() throws {
// given
let token = Token(value: "abc", expireTime: "2750-06-22T22:05:58.81Z")
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let expiration = try XCTUnwrap(formatter.date(from: "2750-06-22T22:05:58.81Z"))
let token = Token(value: "abc", expiresAt: expiration)

// when
let valid = token.isValid

// then
XCTAssertTrue(valid)
}

// MARK: - Decoding

func testDecodeBasicAuthToken() throws {
// given
let jsonText = """
{
"token": "abc",
"expires": "2750-06-22T22:05:58.81Z"
}
"""
let jsonData = try XCTUnwrap(jsonText.data(using: .utf8))
let decoder = JSONDecoder()

// when
let actual = try decoder.decode(Token.self, from: jsonData)

// then
XCTAssertEqual(actual.value, "abc")
XCTAssertNotNil(actual.expiresAt)
XCTAssertTrue(actual.isValid)
}

func testDecodeExpiredBasicAuthToken() throws {
// given
let jsonText = """
{
"token": "abc",
"expires": "1970-10-24T22:05:58.81Z"
}
"""
let jsonData = try XCTUnwrap(jsonText.data(using: .utf8))
let decoder = JSONDecoder()

// when
let actual = try decoder.decode(Token.self, from: jsonData)

// then
XCTAssertEqual(actual.value, "abc")
XCTAssertNotNil(actual.expiresAt)
XCTAssertFalse(actual.isValid)
}

func testDecodeClientCredentialsAuthToken() throws {
// given
let jsonText = """
{
"access_token": "abc",
"scope": "api-role:2",
"token_type": "Bearer",
"expires_in": 599
}
"""
let jsonData = try XCTUnwrap(jsonText.data(using: .utf8))
let decoder = JSONDecoder()

// when
let actual = try decoder.decode(Token.self, from: jsonData)

// then
XCTAssertEqual(actual.value, "abc")
XCTAssertNotNil(actual.expiresAt)
XCTAssertTrue(actual.isValid)
}
}
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ Profiles can be saved locally either signed or unsigned.

## Upload to Jamf Pro

PPPC Utility can use bearer token authentication (or basic authentication as a fallback for versions of Jamf Pro older than v10.34) to any supported
Jamf Pro version using the username and password of a Jamf Pro user account. The user account at minimum needs the two privileges indicated below.

Jamf Pro 10.49 and higher can use OAuth client credentials to access the API. The client ID and client secret generated by Jamf Pro in the
"API Roles and clients" settings are used during the PPPC Utility upload process. When setting up the API Role, these are the permissions that
PPPC Utility requires to upload the profiles.

#### Required API Permissions

- "Create macOS Configuration Profiles" - primary permission to upload profiles; each upload from PPPC Utility creates a new profile.
- "Read Activation Code" - needed to retrieve the organization name that is placed in the profile.

### Jamf Pro 10.7.1 and newer

Starting in Jamf Pro 10.7.1 the Privacy Preferences Policy Control Payload can be uploaded to the API without being signed before uploading.
Expand Down
Loading