git.iko.soy/typeable/oauth2-proxy
on 3d8b59e help sign in
236 lines 5.6 kB history raw
1package providers
3import (
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7 "log"
8 "net/http"
9 "net/url"
10 "path"
11 "strings"
14type GitHubProvider struct {
15 *ProviderData
16 Org string
17 Team string
20func NewGitHubProvider(p *ProviderData) *GitHubProvider {
21 p.ProviderName = "GitHub"
22 if p.LoginURL == nil || p.LoginURL.String() == "" {
23 p.LoginURL = &url.URL{
24 Scheme: "https",
25 Host: "github.com",
26 Path: "/login/oauth/authorize",
27 }
28 }
29 if p.RedeemURL == nil || p.RedeemURL.String() == "" {
30 p.RedeemURL = &url.URL{
31 Scheme: "https",
32 Host: "github.com",
33 Path: "/login/oauth/access_token",
34 }
35 }
36 // ValidationURL is the API Base URL
37 if p.ValidateURL == nil || p.ValidateURL.String() == "" {
38 p.ValidateURL = &url.URL{
39 Scheme: "https",
40 Host: "api.github.com",
41 Path: "/",
42 }
43 }
44 if p.Scope == "" {
45 p.Scope = "user:email"
46 }
47 return &GitHubProvider{ProviderData: p}
49func (p *GitHubProvider) SetOrgTeam(org, team string) {
50 p.Org = org
51 p.Team = team
52 if org != "" || team != "" {
53 p.Scope += " read:org"
54 }
57func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
58 // https://developer.github.com/v3/orgs/#list-your-organizations
60 var orgs []struct {
61 Login string `json:"login"`
62 }
64 params := url.Values{
65 "limit": {"100"},
66 }
68 endpoint := &url.URL{
69 Scheme: p.ValidateURL.Scheme,
70 Host: p.ValidateURL.Host,
71 Path: path.Join(p.ValidateURL.Path, "/user/orgs"),
72 RawQuery: params.Encode(),
73 }
74 req, _ := http.NewRequest("GET", endpoint.String(), nil)
75 req.Header.Set("Accept", "application/vnd.github.v3+json")
76 req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
77 resp, err := http.DefaultClient.Do(req)
78 if err != nil {
79 return false, err
80 }
82 body, err := ioutil.ReadAll(resp.Body)
83 resp.Body.Close()
84 if err != nil {
85 return false, err
86 }
87 if resp.StatusCode != 200 {
88 return false, fmt.Errorf(
89 "got %d from %q %s", resp.StatusCode, endpoint.String(), body)
90 }
92 if err := json.Unmarshal(body, &orgs); err != nil {
93 return false, err
94 }
96 var presentOrgs []string
97 for _, org := range orgs {
98 if p.Org == org.Login {
99 log.Printf("Found Github Organization: %q", org.Login)
100 return true, nil
101 }
102 presentOrgs = append(presentOrgs, org.Login)
103 }
105 log.Printf("Missing Organization:%q in %v", p.Org, presentOrgs)
106 return false, nil
109func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
110 // https://developer.github.com/v3/orgs/teams/#list-user-teams
112 var teams []struct {
113 Name string `json:"name"`
114 Slug string `json:"slug"`
115 Org struct {
116 Login string `json:"login"`
117 } `json:"organization"`
118 }
120 params := url.Values{
121 "limit": {"100"},
122 }
124 endpoint := &url.URL{
125 Scheme: p.ValidateURL.Scheme,
126 Host: p.ValidateURL.Host,
127 Path: path.Join(p.ValidateURL.Path, "/user/teams"),
128 RawQuery: params.Encode(),
129 }
130 req, _ := http.NewRequest("GET", endpoint.String(), nil)
131 req.Header.Set("Accept", "application/vnd.github.v3+json")
132 req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
133 resp, err := http.DefaultClient.Do(req)
134 if err != nil {
135 return false, err
136 }
138 body, err := ioutil.ReadAll(resp.Body)
139 resp.Body.Close()
140 if err != nil {
141 return false, err
142 }
143 if resp.StatusCode != 200 {
144 return false, fmt.Errorf(
145 "got %d from %q %s", resp.StatusCode, endpoint.String(), body)
146 }
148 if err := json.Unmarshal(body, &teams); err != nil {
149 return false, fmt.Errorf("%s unmarshaling %s", err, body)
150 }
152 var hasOrg bool
153 presentOrgs := make(map[string]bool)
154 var presentTeams []string
155 for _, team := range teams {
156 presentOrgs[team.Org.Login] = true
157 if p.Org == team.Org.Login {
158 hasOrg = true
159 ts := strings.Split(p.Team, ",")
160 for _, t := range ts {
161 if t == team.Slug {
162 log.Printf("Found Github Organization:%q Team:%q (Name:%q)", team.Org.Login, team.Slug, team.Name)
163 return true, nil
164 }
165 }
166 presentTeams = append(presentTeams, team.Slug)
167 }
168 }
169 if hasOrg {
170 log.Printf("Missing Team:%q from Org:%q in teams: %v", p.Team, p.Org, presentTeams)
171 } else {
172 var allOrgs []string
173 for org, _ := range presentOrgs {
174 allOrgs = append(allOrgs, org)
175 }
176 log.Printf("Missing Organization:%q in %#v", p.Org, allOrgs)
177 }
178 return false, nil
181func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) {
183 var emails []struct {
184 Email string `json:"email"`
185 Primary bool `json:"primary"`
186 }
188 // if we require an Org or Team, check that first
189 if p.Org != "" {
190 if p.Team != "" {
191 if ok, err := p.hasOrgAndTeam(s.AccessToken); err != nil || !ok {
192 return "", err
193 }
194 } else {
195 if ok, err := p.hasOrg(s.AccessToken); err != nil || !ok {
196 return "", err
197 }
198 }
199 }
201 endpoint := &url.URL{
202 Scheme: p.ValidateURL.Scheme,
203 Host: p.ValidateURL.Host,
204 Path: path.Join(p.ValidateURL.Path, "/user/emails"),
205 }
206 req, _ := http.NewRequest("GET", endpoint.String(), nil)
207 req.Header.Set("Authorization", fmt.Sprintf("token %s", s.AccessToken))
208 resp, err := http.DefaultClient.Do(req)
209 if err != nil {
210 return "", err
211 }
212 body, err := ioutil.ReadAll(resp.Body)
213 resp.Body.Close()
214 if err != nil {
215 return "", err
216 }
218 if resp.StatusCode != 200 {
219 return "", fmt.Errorf("got %d from %q %s",
220 resp.StatusCode, endpoint.String(), body)
221 } else {
222 log.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
223 }
225 if err := json.Unmarshal(body, &emails); err != nil {
226 return "", fmt.Errorf("%s unmarshaling %s", err, body)
227 }
229 for _, email := range emails {
230 if email.Primary {
231 return email.Email, nil
232 }
233 }
235 return "", nil