git.iko.soy/typeable/oauth2-proxy
on 3155ada help sign in
304 lines 7.3 kB history raw
1package providers
3import (
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7 "net/http"
8 "net/url"
9 "path"
10 "strconv"
11 "strings"
13 "github.com/pusher/oauth2_proxy/logger"
14 "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
17// GitHubProvider represents an GitHub based Identity Provider
18type GitHubProvider struct {
19 *ProviderData
20 Org string
21 Team string
24// NewGitHubProvider initiates a new GitHubProvider
25func NewGitHubProvider(p *ProviderData) *GitHubProvider {
26 p.ProviderName = "GitHub"
27 if p.LoginURL == nil || p.LoginURL.String() == "" {
28 p.LoginURL = &url.URL{
29 Scheme: "https",
30 Host: "github.com",
31 Path: "/login/oauth/authorize",
32 }
33 }
34 if p.RedeemURL == nil || p.RedeemURL.String() == "" {
35 p.RedeemURL = &url.URL{
36 Scheme: "https",
37 Host: "github.com",
38 Path: "/login/oauth/access_token",
39 }
40 }
41 // ValidationURL is the API Base URL
42 if p.ValidateURL == nil || p.ValidateURL.String() == "" {
43 p.ValidateURL = &url.URL{
44 Scheme: "https",
45 Host: "api.github.com",
46 Path: "/",
47 }
48 }
49 if p.Scope == "" {
50 p.Scope = "user:email"
51 }
52 return &GitHubProvider{ProviderData: p}
55// SetOrgTeam adds GitHub org reading parameters to the OAuth2 scope
56func (p *GitHubProvider) SetOrgTeam(org, team string) {
57 p.Org = org
58 p.Team = team
59 if org != "" || team != "" {
60 p.Scope += " read:org"
61 }
64func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
65 // https://developer.github.com/v3/orgs/#list-your-organizations
67 var orgs []struct {
68 Login string `json:"login"`
69 }
71 type orgsPage []struct {
72 Login string `json:"login"`
73 }
75 pn := 1
76 for {
77 params := url.Values{
78 "limit": {"200"},
79 "page": {strconv.Itoa(pn)},
80 }
82 endpoint := &url.URL{
83 Scheme: p.ValidateURL.Scheme,
84 Host: p.ValidateURL.Host,
85 Path: path.Join(p.ValidateURL.Path, "/user/orgs"),
86 RawQuery: params.Encode(),
87 }
88 req, _ := http.NewRequest("GET", endpoint.String(), nil)
89 req.Header.Set("Accept", "application/vnd.github.v3+json")
90 req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
91 resp, err := http.DefaultClient.Do(req)
92 if err != nil {
93 return false, err
94 }
96 body, err := ioutil.ReadAll(resp.Body)
97 resp.Body.Close()
98 if err != nil {
99 return false, err
100 }
101 if resp.StatusCode != 200 {
102 return false, fmt.Errorf(
103 "got %d from %q %s", resp.StatusCode, endpoint.String(), body)
104 }
106 var op orgsPage
107 if err := json.Unmarshal(body, &op); err != nil {
108 return false, err
109 }
110 if len(op) == 0 {
111 break
112 }
114 orgs = append(orgs, op...)
115 pn++
116 }
118 var presentOrgs []string
119 for _, org := range orgs {
120 if p.Org == org.Login {
121 logger.Printf("Found Github Organization: %q", org.Login)
122 return true, nil
123 }
124 presentOrgs = append(presentOrgs, org.Login)
125 }
127 logger.Printf("Missing Organization:%q in %v", p.Org, presentOrgs)
128 return false, nil
131func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
132 // https://developer.github.com/v3/orgs/teams/#list-user-teams
134 var teams []struct {
135 Name string `json:"name"`
136 Slug string `json:"slug"`
137 Org struct {
138 Login string `json:"login"`
139 } `json:"organization"`
140 }
142 params := url.Values{
143 "limit": {"200"},
144 }
146 endpoint := &url.URL{
147 Scheme: p.ValidateURL.Scheme,
148 Host: p.ValidateURL.Host,
149 Path: path.Join(p.ValidateURL.Path, "/user/teams"),
150 RawQuery: params.Encode(),
151 }
152 req, _ := http.NewRequest("GET", endpoint.String(), nil)
153 req.Header.Set("Accept", "application/vnd.github.v3+json")
154 req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
155 resp, err := http.DefaultClient.Do(req)
156 if err != nil {
157 return false, err
158 }
160 body, err := ioutil.ReadAll(resp.Body)
161 resp.Body.Close()
162 if err != nil {
163 return false, err
164 }
165 if resp.StatusCode != 200 {
166 return false, fmt.Errorf(
167 "got %d from %q %s", resp.StatusCode, endpoint.String(), body)
168 }
170 if err := json.Unmarshal(body, &teams); err != nil {
171 return false, fmt.Errorf("%s unmarshaling %s", err, body)
172 }
174 var hasOrg bool
175 presentOrgs := make(map[string]bool)
176 var presentTeams []string
177 for _, team := range teams {
178 presentOrgs[team.Org.Login] = true
179 if p.Org == team.Org.Login {
180 hasOrg = true
181 ts := strings.Split(p.Team, ",")
182 for _, t := range ts {
183 if t == team.Slug {
184 logger.Printf("Found Github Organization:%q Team:%q (Name:%q)", team.Org.Login, team.Slug, team.Name)
185 return true, nil
186 }
187 }
188 presentTeams = append(presentTeams, team.Slug)
189 }
190 }
191 if hasOrg {
192 logger.Printf("Missing Team:%q from Org:%q in teams: %v", p.Team, p.Org, presentTeams)
193 } else {
194 var allOrgs []string
195 for org := range presentOrgs {
196 allOrgs = append(allOrgs, org)
197 }
198 logger.Printf("Missing Organization:%q in %#v", p.Org, allOrgs)
199 }
200 return false, nil
203// GetEmailAddress returns the Account email address
204func (p *GitHubProvider) GetEmailAddress(s *sessions.SessionState) (string, error) {
206 var emails []struct {
207 Email string `json:"email"`
208 Primary bool `json:"primary"`
209 Verified bool `json:"verified"`
210 }
212 // if we require an Org or Team, check that first
213 if p.Org != "" {
214 if p.Team != "" {
215 if ok, err := p.hasOrgAndTeam(s.AccessToken); err != nil || !ok {
216 return "", err
217 }
218 } else {
219 if ok, err := p.hasOrg(s.AccessToken); err != nil || !ok {
220 return "", err
221 }
222 }
223 }
225 endpoint := &url.URL{
226 Scheme: p.ValidateURL.Scheme,
227 Host: p.ValidateURL.Host,
228 Path: path.Join(p.ValidateURL.Path, "/user/emails"),
229 }
230 req, _ := http.NewRequest("GET", endpoint.String(), nil)
231 req.Header.Set("Authorization", fmt.Sprintf("token %s", s.AccessToken))
232 resp, err := http.DefaultClient.Do(req)
233 if err != nil {
234 return "", err
235 }
236 body, err := ioutil.ReadAll(resp.Body)
237 resp.Body.Close()
238 if err != nil {
239 return "", err
240 }
242 if resp.StatusCode != 200 {
243 return "", fmt.Errorf("got %d from %q %s",
244 resp.StatusCode, endpoint.String(), body)
245 }
247 logger.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
249 if err := json.Unmarshal(body, &emails); err != nil {
250 return "", fmt.Errorf("%s unmarshaling %s", err, body)
251 }
253 for _, email := range emails {
254 if email.Primary && email.Verified {
255 return email.Email, nil
256 }
257 }
259 return "", nil
262// GetUserName returns the Account user name
263func (p *GitHubProvider) GetUserName(s *sessions.SessionState) (string, error) {
264 var user struct {
265 Login string `json:"login"`
266 Email string `json:"email"`
267 }
269 endpoint := &url.URL{
270 Scheme: p.ValidateURL.Scheme,
271 Host: p.ValidateURL.Host,
272 Path: path.Join(p.ValidateURL.Path, "/user"),
273 }
275 req, err := http.NewRequest("GET", endpoint.String(), nil)
276 if err != nil {
277 return "", fmt.Errorf("could not create new GET request: %v", err)
278 }
280 req.Header.Set("Authorization", fmt.Sprintf("token %s", s.AccessToken))
281 resp, err := http.DefaultClient.Do(req)
282 if err != nil {
283 return "", err
284 }
286 body, err := ioutil.ReadAll(resp.Body)
287 defer resp.Body.Close()
288 if err != nil {
289 return "", err
290 }
292 if resp.StatusCode != 200 {
293 return "", fmt.Errorf("got %d from %q %s",
294 resp.StatusCode, endpoint.String(), body)
295 }
297 logger.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
299 if err := json.Unmarshal(body, &user); err != nil {
300 return "", fmt.Errorf("%s unmarshaling %s", err, body)
301 }
303 return user.Login, nil