Skip to content

Commit 0aee657

Browse files
authored
Merge pull request #4 from hortoncd/add_other_installation_types
Add other installation types rather than just repository
2 parents d7db365 + f09cccc commit 0aee657

File tree

8 files changed

+258
-20
lines changed

8 files changed

+258
-20
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog
2+
3+
## 0.3.0
4+
- deprecates app_* methods that were only repository installations
5+
- adds methods to do organization, repository, and user installations
6+
7+
## 0.2.0
8+
- changed lib path to `github-app-auth` instead of `github/app/auth`
9+
10+
## 0.1.0
11+
- initial release with support for repository installation authentication

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
github-app-auth (0.2.0)
4+
github-app-auth (0.3.0)
55
jwt (~> 2.7)
66
octokit (~> 6.1)
77
openssl (~> 3.1)

README.md

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,44 @@ See [the GitHub documentation](https://docs.github.com/en/apps/creating-github-a
6767

6868
The examples are using the gem as an includable module, but can also be used with the available AuthClass class..
6969

70-
Auth as an application installation for a repo and return an Octokit::Client.
70+
There are several methods of authenticating as an application installation.
71+
72+
#### Organization Installation
73+
74+
Auth as an application installation for an organization and return an Octokit::Client.
75+
```
76+
client = organization_installation_client("myorg")
77+
```
78+
79+
Alternatively you can retrieve the token, and then set up your own GitHub client (Octokit or whatever you prefer) as needed.
80+
```
81+
token = organization_installation_token("myorg")
82+
client = Octokit::Client.new({ bearer_token: token, ... })
83+
```
84+
85+
#### Repository Installation
86+
87+
Auth as an application installation for a repository and return an Octokit::Client.
88+
```
89+
client = repository_installation_client("myaccount/myrepo")
90+
```
91+
92+
Alternatively you can retrieve the token, and then set up your own GitHub client (Octokit or whatever you prefer) as needed.
93+
```
94+
token = repository_installation_token("myaccount/myrepo")
95+
client = Octokit::Client.new({ bearer_token: token, ... })
96+
```
97+
98+
#### User Installation
99+
100+
Auth as an application installation for a user and return an Octokit::Client.
71101
```
72-
client = app_installation_client("myaccount/myrepo")
102+
client = user_installation_client("myorg")
73103
```
74104

75105
Alternatively you can retrieve the token, and then set up your own GitHub client (Octokit or whatever you prefer) as needed.
76106
```
77-
token = app_instalation_token("myaccount/myrepo")
107+
token = user_installation_token("myorg")
78108
client = Octokit::Client.new({ bearer_token: token, ... })
79109
```
80110

github-app-auth.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
1616
spec.metadata["source_code_uri"] = "#{spec.homepage}"
1717
spec.metadata["changelog_uri"] = "#{spec.homepage}"
1818

19-
spec.files = Dir.glob("lib/**/*") + %w[CODE_OF_CONDUCT.md LICENSE.txt README.md]
19+
spec.files = Dir.glob("lib/**/*") + %w[CHANGELOG.md CODE_OF_CONDUCT.md LICENSE.txt README.md]
2020
spec.require_paths = ["lib"]
2121
spec.add_dependency "jwt", "~> 2.7"
2222
spec.add_dependency "octokit", "~> 6.1"

lib/github-app-auth.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
module GitHub
66
module App
77
module Auth
8+
class Error < StandardError; end
9+
class InstallationError < Error; end
10+
class TokenError< Error; end
11+
812
class AuthClass
913
include GitHub::App::Auth
1014
end

lib/github-app-auth/app_installation.rb

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,65 @@
11
module GitHub
22
module App
33
module Auth
4+
# legacy support because original only supported repo
45
def app_installation_client(repo, options = {})
6+
puts "DEPRECATED: app_installation_client will be removed in v0.4.0, use repository_installation_client instead"
57
client(bearer_token: app_installation_token(repo, options))
68
end
79

810
def app_installation_token(repo, options = {})
9-
application_client = app_client
10-
installation = application_client.find_repository_installation(repo)
11+
puts "DEPRECATED: app_installation_token will be removed in v0.4.0, use repository_installation_token instead"
12+
installation_token(:repository, repo, options)
13+
end
14+
15+
def organization_installation_client(org, options = {})
16+
client(bearer_token: organization_installation_token(org, options))
17+
end
18+
19+
def organization_installation_token(org, options = {})
20+
installation_token(:organization, org, options)
21+
end
22+
23+
def repository_installation_client(repo, options = {})
24+
client(bearer_token: repository_installation_token(repo, options))
25+
end
26+
27+
def repository_installation_token(repo, options = {})
28+
installation_token(:repository, repo, options)
29+
end
30+
31+
def user_installation_client(user, options = {})
32+
client(bearer_token: user_installation_token(user, options))
33+
end
34+
35+
def user_installation_token(user, options = {})
36+
installation_token(:user, user, options)
37+
end
38+
39+
# Supported types are :organization, :repository, :user
40+
def installation_token(type, name, options = {})
41+
application_client = app_client(options)
42+
installation = begin
43+
case type
44+
when :organization
45+
application_client.find_organization_installation(name)
46+
when :repository
47+
application_client.find_repository_installation(name)
48+
when :user
49+
application_client.find_user_installation(name)
50+
else
51+
raise ArgumentError, "Unsupported installation type: #{type}"
52+
end
53+
end
54+
55+
if installation.nil? || installation[:id].nil?
56+
raise GitHub::App::Auth::InstallationError, "Could not find installation for #{type}: #{name}"
57+
end
58+
1159
resp = application_client.create_app_installation_access_token(installation[:id])
60+
if resp.nil? || resp[:token].nil?
61+
raise GitHub::App::Auth::TokenError, "Could generate installation token for #{type}: #{name}"
62+
end
1263
resp[:token]
1364
end
1465
end

lib/github-app-auth/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module GitHub
22
module App
33
module Auth
4-
VERSION = "0.2.0"
4+
VERSION = "0.3.0"
55
end
66
end
77
end

spec/github/app/auth/app_installation_spec.rb

Lines changed: 154 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,176 @@
44
# Use the class we provide to test the module
55
subject { GitHub::App::Auth::AuthClass.new }
66

7-
describe ".app_installation_client" do
8-
let(:repo) { "test/test-repo" }
7+
let(:github_client) { instance_double(Octokit::Client) }
8+
let(:installation_id) { "54321" }
9+
let(:org) { "test-org" }
10+
let(:repo) { "test/test-repo" }
11+
let(:token) { "test-token" }
12+
let(:user) { "test-user" }
913

10-
it "returns an Octokit::Client authorized to an app" do
14+
describe ".app_installation_client" do
15+
it "returns an Octokit::Client authorized to an app installation" do
1116
expect(subject).to receive(:app_installation_token)
12-
.with(repo, {})
13-
.and_return("test-token")
17+
.with(repo, {})
18+
.and_return("test-token")
1419
expect(subject.app_installation_client(repo)).to be_kind_of(Octokit::Client)
1520
end
21+
22+
it "returns an Octokit::Client authorized to an app installation" do
23+
expect(subject).to receive(:app_installation_token)
24+
.with(repo, {})
25+
.and_return("test-token")
26+
expected_output = "DEPRECATED: app_installation_client will be removed in v0.4.0, use repository_installation_client instead\n"
27+
expect { subject.app_installation_client(repo) }.to output(expected_output).to_stdout
28+
end
1629
end
1730

1831
describe ".app_installation_token" do
19-
let(:github_client) { instance_double(Octokit::Client) }
32+
it "returns a JWT token for for an app" do
33+
expect(subject).to receive(:app_client)
34+
.and_return(github_client)
35+
expect(github_client).to receive(:find_repository_installation)
36+
.with(repo)
37+
.and_return(id: installation_id)
38+
expect(github_client).to receive(:create_app_installation_access_token)
39+
.with(installation_id)
40+
.and_return(token: token)
41+
expect(subject.app_installation_token(repo)).to eq(token)
42+
end
2043

21-
let(:installation_id) { "54321" }
44+
it "outputs a deprecation notice" do
45+
expect(subject).to receive(:app_client)
46+
.and_return(github_client)
47+
expect(github_client).to receive(:find_repository_installation)
48+
.with(repo)
49+
.and_return(id: installation_id)
50+
expect(github_client).to receive(:create_app_installation_access_token)
51+
.with(installation_id)
52+
.and_return(token: token)
53+
expected_output = [
54+
"DEPRECATED: app_installation_client will be removed in v0.4.0, use repository_installation_client instead",
55+
"DEPRECATED: app_installation_token will be removed in v0.4.0, use repository_installation_token instead\n"
56+
].join("\n")
57+
expect { subject.app_installation_client(repo) }.to output(expected_output).to_stdout
58+
end
59+
end
2260

23-
let(:repo) { "test/test-repo" }
61+
describe ".organization_installation_client" do
62+
it "returns an Octokit::Client authorized to an organization installation" do
63+
expect(subject).to receive(:organization_installation_token)
64+
.with(org, {})
65+
.and_return(token)
66+
expect(subject.organization_installation_client(org)).to be_kind_of(Octokit::Client)
67+
end
68+
end
2469

70+
describe ".organization_installation_token" do
2571
it "returns a JWT token for for an app" do
2672
expect(subject).to receive(:app_client)
27-
.and_return(github_client)
73+
.and_return(github_client)
74+
expect(github_client).to receive(:find_organization_installation)
75+
.with(org)
76+
.and_return(id: installation_id)
77+
expect(github_client).to receive(:create_app_installation_access_token)
78+
.with(installation_id)
79+
.and_return(token: token)
80+
expect(subject.organization_installation_token(org)).to eq("test-token")
81+
end
82+
end
83+
84+
describe ".repository_installation_client" do
85+
it "returns an Octokit::Client authorized to a repo installation" do
86+
expect(subject).to receive(:repository_installation_token)
87+
.with(repo, {})
88+
.and_return(token)
89+
expect(subject.repository_installation_client(repo)).to be_kind_of(Octokit::Client)
90+
end
91+
end
92+
93+
describe ".repository_installation_token" do
94+
it "returns a JWT token for for a repo" do
95+
expect(subject).to receive(:app_client)
96+
.and_return(github_client)
97+
expect(github_client).to receive(:find_repository_installation)
98+
.with(repo)
99+
.and_return(id: installation_id)
100+
expect(github_client).to receive(:create_app_installation_access_token)
101+
.with(installation_id)
102+
.and_return(token: token)
103+
expect(subject.repository_installation_token(repo)).to eq(token)
104+
end
105+
end
106+
107+
describe ".user_installation_client" do
108+
it "returns an Octokit::Client authorized to a user installation" do
109+
expect(subject).to receive(:user_installation_token)
110+
.with(user, {})
111+
.and_return(token)
112+
expect(subject.user_installation_client(user)).to be_kind_of(Octokit::Client)
113+
end
114+
end
115+
116+
describe ".user_installation_token" do
117+
it "returns a JWT token for for a user" do
118+
expect(subject).to receive(:app_client)
119+
.and_return(github_client)
120+
expect(github_client).to receive(:find_user_installation)
121+
.with(user)
122+
.and_return(id: installation_id)
123+
expect(github_client).to receive(:create_app_installation_access_token)
124+
.with(installation_id)
125+
.and_return(token: token)
126+
expect(subject.user_installation_token(user)).to eq(token)
127+
end
128+
end
129+
130+
describe ".installation_token" do
131+
it "raises an error for unsupported installation type" do
132+
expect(subject).to receive(:app_client)
133+
.and_return(github_client)
134+
expect { subject.installation_token("unsupported", {}) }.to raise_error(ArgumentError)
135+
end
136+
137+
it "raises an error if no installation id is found" do
138+
expect(subject).to receive(:app_client)
139+
.and_return(github_client)
140+
expect(github_client).to receive(:find_repository_installation)
141+
.with(repo)
142+
.and_return(token: nil)
143+
expect { subject.installation_token(:repository, repo, {}) }.to raise_error(GitHub::App::Auth::InstallationError)
144+
end
145+
146+
it "raises an error if no installation is returned" do
147+
expect(subject).to receive(:app_client)
148+
.and_return(github_client)
149+
expect(github_client).to receive(:find_repository_installation)
150+
.with(repo)
151+
.and_return(nil)
152+
expect { subject.installation_token(:repository, repo, {}) }.to raise_error(GitHub::App::Auth::InstallationError)
153+
end
154+
155+
it "raises an error if no token is returned" do
156+
expect(subject).to receive(:app_client)
157+
.and_return(github_client)
158+
expect(github_client).to receive(:find_repository_installation)
159+
.with(repo)
160+
.and_return(id: installation_id)
161+
expect(github_client).to receive(:create_app_installation_access_token)
162+
.with(installation_id)
163+
.and_return(token: nil)
164+
expect { subject.installation_token(:repository, repo, {}) }.to raise_error(GitHub::App::Auth::TokenError)
165+
end
166+
167+
it "raises an error if resp is nil for token creation" do
168+
expect(subject).to receive(:app_client)
169+
.and_return(github_client)
28170
expect(github_client).to receive(:find_repository_installation)
29-
.with("test/test-repo")
171+
.with(repo)
30172
.and_return(id: installation_id)
31173
expect(github_client).to receive(:create_app_installation_access_token)
32174
.with(installation_id)
33-
.and_return(token: "test-token")
34-
expect(subject.app_installation_token(repo)).to eq("test-token")
175+
.and_return(nil)
176+
expect { subject.installation_token(:repository, repo, {}) }.to raise_error(GitHub::App::Auth::TokenError)
35177
end
36178
end
37179
end

0 commit comments

Comments
 (0)