├── .gitignore ├── SECURITY.md ├── .github └── workflows │ ├── swift.yml │ └── super-linter.yml ├── README.md ├── Tests └── AuthorizationTests │ └── AuthorizationTests.swift ├── LICENSE ├── Package.swift └── Sources └── Authorization └── Authorization.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/ 8 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 0.1.x | :white_check_mark: | 8 | | < 0.1 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Contact: mailto:44uaanjm0@relay.firefox.com 13 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: swift build -v 18 | - name: Run tests 19 | run: swift test -v 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # authorization-swift 2 | 3 | Swift wrapper on `AuthorizationExecuteWithPrivileges`. 4 | 5 | ## Example 6 | 7 | ```swift 8 | import Authorization 9 | 10 | func main() throws { 11 | let fileHandler = try Authorization.executeWithPrivileges("/bin/ls /").get() 12 | print(String(bytes: fileHandler.readDataToEndOfFile(), encoding: .utf8)!) 13 | } 14 | 15 | main() 16 | ``` 17 | -------------------------------------------------------------------------------- /Tests/AuthorizationTests/AuthorizationTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Authorization 3 | 4 | final class AuthorizationTests: XCTestCase { 5 | func testExecuteWithPrivileges() throws { 6 | throw XCTSkip("user required") 7 | 8 | let fh = try Authorization.executeWithPrivileges("/bin/ls /").get() 9 | print(String(bytes: fh.readDataToEndOfFile(), encoding: .utf8)!) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/super-linter.yml: -------------------------------------------------------------------------------- 1 | # This workflow executes several linters on changed files based on languages used in your code base whenever 2 | # you push a code or open a pull request. 3 | # 4 | # You can adjust the behavior by modifying this file. 5 | # For more information, see: 6 | # https://github.com/github/super-linter 7 | name: Lint Code Base 8 | 9 | on: 10 | push: 11 | branches: [ master ] 12 | pull_request: 13 | branches: [ master ] 14 | jobs: 15 | run-lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | with: 21 | # Full git history is needed to get a proper list of changed files within `super-linter` 22 | fetch-depth: 0 23 | 24 | - name: Lint Code Base 25 | uses: github/super-linter@v4 26 | env: 27 | VALIDATE_ALL_CODEBASE: false 28 | DEFAULT_BRANCH: master 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 lucky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Authorization", 8 | products: [ 9 | // Products define the executables and libraries a package produces, and make them visible to other packages. 10 | .library( 11 | name: "Authorization", 12 | targets: ["Authorization"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 21 | .target( 22 | name: "Authorization", 23 | dependencies: []), 24 | .testTarget( 25 | name: "AuthorizationTests", 26 | dependencies: ["Authorization"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Sources/Authorization/Authorization.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Swift 3 | 4 | // https://github.com/sveinbjornt/STPrivilegedTask/blob/master/STPrivilegedTask.m 5 | // https://github.com/gui-dos/Guigna/blob/9fdd75ca0337c8081e2a2727960389c7dbf8d694/Legacy/Guigna-Swift/Guigna/GAgent.swift#L42-L80 6 | 7 | public struct Authorization { 8 | 9 | public enum Error: Swift.Error { 10 | case create(OSStatus) 11 | case copyRights(OSStatus) 12 | case exec(OSStatus) 13 | } 14 | 15 | public static func executeWithPrivileges( 16 | _ command: String 17 | ) -> Result { 18 | 19 | let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2) 20 | var fn: @convention(c) ( 21 | AuthorizationRef, 22 | UnsafePointer, // path 23 | AuthorizationFlags, 24 | UnsafePointer?>, // args 25 | UnsafeMutablePointer>? 26 | ) -> OSStatus 27 | fn = unsafeBitCast( 28 | dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges"), 29 | to: type(of: fn) 30 | ) 31 | 32 | var authorizationRef: AuthorizationRef? = nil 33 | var err = AuthorizationCreate(nil, nil, [], &authorizationRef) 34 | guard err == errAuthorizationSuccess else { 35 | return .failure(.create(err)) 36 | } 37 | defer { AuthorizationFree(authorizationRef!, [.destroyRights]) } 38 | 39 | var components = command.components(separatedBy: " ") 40 | var path = components.remove(at: 0).cString(using: .utf8)! 41 | let name = kAuthorizationRightExecute.cString(using: .utf8)! 42 | 43 | var items: AuthorizationItem = name.withUnsafeBufferPointer { nameBuf in 44 | path.withUnsafeBufferPointer { pathBuf in 45 | let pathPtr = 46 | UnsafeMutableRawPointer(mutating: pathBuf.baseAddress!) 47 | return AuthorizationItem( 48 | name: nameBuf.baseAddress!, 49 | valueLength: path.count, 50 | value: pathPtr, 51 | flags: 0 52 | ) 53 | } 54 | } 55 | 56 | var rights: AuthorizationRights = 57 | withUnsafeMutablePointer(to: &items) { items in 58 | return AuthorizationRights(count: 1, items: items) 59 | } 60 | 61 | let flags: AuthorizationFlags = [ 62 | .interactionAllowed, 63 | .preAuthorize, 64 | .extendRights, 65 | ] 66 | 67 | err = AuthorizationCopyRights( 68 | authorizationRef!, 69 | &rights, 70 | nil, 71 | flags, 72 | nil 73 | ) 74 | guard err == errAuthorizationSuccess else { 75 | return .failure(.copyRights(err)) 76 | } 77 | 78 | let rest = components.map { $0.cString(using: .utf8)! } 79 | var args = Array?>( 80 | repeating: nil, 81 | count: rest.count + 1 82 | ) 83 | for (idx, arg) in rest.enumerated() { 84 | args[idx] = UnsafePointer?(arg) 85 | } 86 | 87 | var file = FILE() 88 | let fh: FileHandle? 89 | 90 | (err, fh) = withUnsafeMutablePointer(to: &file) { file in 91 | var pipe = file 92 | let err = fn(authorizationRef!, &path, [], &args, &pipe) 93 | guard err == errAuthorizationSuccess else { 94 | return (err, nil) 95 | } 96 | let fh = FileHandle( 97 | fileDescriptor: fileno(pipe), 98 | closeOnDealloc: true 99 | ) 100 | return (err, fh) 101 | } 102 | guard err == errAuthorizationSuccess else { 103 | return .failure(.exec(err)) 104 | } 105 | return .success(fh!) 106 | } 107 | } 108 | --------------------------------------------------------------------------------