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

Updates GitHub OAUTH2 to restrict access based on organizations or teams within organizations #128

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
25 changes: 23 additions & 2 deletions api/v1/auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package auth

import (
"net/http"
"os"
"strings"
"time"

"github.com/gin-gonic/gin"
"github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
"github.com/vx3r/wg-gen-web/auth"
"github.com/vx3r/wg-gen-web/model"
"github.com/vx3r/wg-gen-web/util"
"golang.org/x/oauth2"
"net/http"
"time"
)

// ApplyRoutes applies router to gin Router
Expand Down Expand Up @@ -95,6 +98,24 @@ func oauth2Exchange(c *gin.Context) {
}

cacheDb.Delete(loginVals.ClientId)

// This is primarily used for github, but can be used for other oauth2 providers where it makes sense.
// Check if user is in org, use OAUTH2_TEAMS if you want to further restrict access within an org. This is optional.
if os.Getenv("OAUTH2_ORG") != "" {

teamsEnv := os.Getenv("OAUTH2_TEAMS")
var teams []string
if teamsEnv != "" {
teams = strings.Split(teamsEnv, ",")
}

inOrg, err := oauth2Client.CheckMembership(oauth2Token, os.Getenv("OAUTH2_ORG"), teams)
if err != nil || !inOrg {
c.AbortWithStatus(http.StatusForbidden)
return
}
}

cacheDb.Set(oauth2Token.AccessToken, oauth2Token, cache.DefaultExpiration)

c.JSON(http.StatusOK, oauth2Token.AccessToken)
Expand Down
3 changes: 2 additions & 1 deletion api/v1/server/server.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package server

import (
"net/http"

"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"github.com/vx3r/wg-gen-web/auth"
"github.com/vx3r/wg-gen-web/core"
"github.com/vx3r/wg-gen-web/model"
"github.com/vx3r/wg-gen-web/version"
"golang.org/x/oauth2"
"net/http"
)

// ApplyRoutes applies router to gin Router
Expand Down
4 changes: 3 additions & 1 deletion auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package auth

import (
"fmt"
"os"

log "github.com/sirupsen/logrus"
"github.com/vx3r/wg-gen-web/auth/fake"
"github.com/vx3r/wg-gen-web/auth/github"
"github.com/vx3r/wg-gen-web/auth/oauth2oidc"
"github.com/vx3r/wg-gen-web/model"
"golang.org/x/oauth2"
"os"
)

// Auth interface to implement as auth provider
Expand All @@ -17,6 +18,7 @@ type Auth interface {
CodeUrl(state string) string
Exchange(code string) (*oauth2.Token, error)
UserInfo(oauth2Token *oauth2.Token) (*model.User, error)
CheckMembership(oauth2Token *oauth2.Token, org string, teams []string) (bool, error)
}

// GetAuthProvider get an instance of auth provider based on config
Expand Down
9 changes: 7 additions & 2 deletions auth/fake/fake.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package fake

import (
"time"

"github.com/vx3r/wg-gen-web/model"
"github.com/vx3r/wg-gen-web/util"
"golang.org/x/oauth2"
"time"
)

// Fake in order to implement interface, struct is required
Expand All @@ -15,6 +16,11 @@ func (o *Fake) Setup() error {
return nil
}

// Check if current user is in given org
func (o *Fake) CheckMembership(oauth2Token *oauth2.Token, org string, teams []string) (bool, error) {
return true, nil
}

// CodeUrl get url to redirect client for auth
func (o *Fake) CodeUrl(state string) string {
return "_magic_string_fake_auth_no_redirect_"
Expand All @@ -41,7 +47,6 @@ func (o *Fake) UserInfo(oauth2Token *oauth2.Token) (*model.User, error) {
Sub: "unknown",
Name: "Unknown",
Email: "unknown",
Profile: "unknown",
Issuer: "unknown",
IssuedAt: time.Time{},
}, nil
Expand Down
60 changes: 52 additions & 8 deletions auth/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/vx3r/wg-gen-web/model"
"golang.org/x/oauth2"
oauth2Github "golang.org/x/oauth2/github"
"io/ioutil"
"net/http"
"os"
"time"

"github.com/vx3r/wg-gen-web/model"
"golang.org/x/oauth2"
oauth2Github "golang.org/x/oauth2/github"
)

// Github in order to implement interface, struct is required
Expand All @@ -26,13 +27,59 @@ func (o *Github) Setup() error {
ClientID: os.Getenv("OAUTH2_CLIENT_ID"),
ClientSecret: os.Getenv("OAUTH2_CLIENT_SECRET"),
RedirectURL: os.Getenv("OAUTH2_REDIRECT_URL"),
Scopes: []string{"user"},
Scopes: []string{"read:user", "user:email", "read:org"},
Endpoint: oauth2Github.Endpoint,
}

return nil
}

// Check if current user is in given org
func (o *Github) CheckMembership(oauth2Token *oauth2.Token, org string, teams []string) (bool, error) {
// we have the token, lets get user information
user, err := o.UserInfo(oauth2Token)
if err != nil {
return false, err
}

client := &http.Client{}

// If teams is empty, check for org membership
if len(teams) == 0 {
url := fmt.Sprintf("https://api.github.com/orgs/%s/members/%s", org, user.Name)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return false, err
}

req.Header.Set("Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken))
resp, err := client.Do(req)

if err == nil && resp.StatusCode == 204 {
return true, nil
}
return false, err
}

// If team slice is not empty, check for team membership
// GET /orgs/{org}/teams/{team_slug}/memberships/{username}
for _, team := range teams {
url := fmt.Sprintf("https://api.github.com/orgs/%s/teams/%s/memberships/%s", org, team, user.Name)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return false, err
}

req.Header.Set("Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken))
resp, err := client.Do(req)

if err == nil && resp.StatusCode == 200 {
return true, nil
}
}
return false, err
}

// CodeUrl get url to redirect client for auth
func (o *Github) CodeUrl(state string) string {
return oauth2Config.AuthCodeURL(state)
Expand Down Expand Up @@ -84,15 +131,12 @@ func (o *Github) UserInfo(oauth2Token *oauth2.Token) (*model.User, error) {
// get some infos about user
user := &model.User{}

if val, ok := data["name"]; ok && val != nil {
if val, ok := data["login"]; ok && val != nil {
user.Name = val.(string)
}
if val, ok := data["email"]; ok && val != nil {
user.Email = val.(string)
}
if val, ok := data["html_url"]; ok && val != nil {
user.Profile = val.(string)
}

// openid specific
user.Sub = "github is not an openid provider"
Expand Down
12 changes: 9 additions & 3 deletions auth/oauth2oidc/oauth2oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package oauth2oidc
import (
"context"
"fmt"
"github.com/coreos/go-oidc"
"os"

"github.com/coreos/go-oidc/v3/oidc"
log "github.com/sirupsen/logrus"
"github.com/vx3r/wg-gen-web/model"
"golang.org/x/oauth2"
"os"
)

// Oauth2idc in order to implement interface, struct is required
Expand Down Expand Up @@ -48,6 +49,12 @@ func (o *Oauth2idc) CodeUrl(state string) string {
return oauth2Config.AuthCodeURL(state)
}

// Check if current user is in given org
func (o *Oauth2idc) CheckMembership(oauth2Token *oauth2.Token, org string, teams []string) (bool, error) {
// TODO: Add real implementation
return false, nil
}

// Exchange exchange code for Oauth2 token
func (o *Oauth2idc) Exchange(code string) (*oauth2.Token, error) {
oauth2Token, err := oauth2Config.Exchange(context.TODO(), code)
Expand Down Expand Up @@ -85,7 +92,6 @@ func (o *Oauth2idc) UserInfo(oauth2Token *oauth2.Token) (*model.User, error) {
user := &model.User{}
user.Sub = userInfo.Subject
user.Email = userInfo.Email
user.Profile = userInfo.Profile

if v, found := claims["name"]; found && v != nil {
user.Name = v.(string)
Expand Down
13 changes: 7 additions & 6 deletions cmd/wg-gen-web/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ package main

import (
"fmt"
"github.com/danielkov/gin-helmet"
"net/http"
"os"
"path/filepath"
"strings"
"time"

helmet "github.com/danielkov/gin-helmet"
"github.com/gin-contrib/cors"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
Expand All @@ -15,11 +21,6 @@ import (
"github.com/vx3r/wg-gen-web/util"
"github.com/vx3r/wg-gen-web/version"
"golang.org/x/oauth2"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)

var (
Expand Down
41 changes: 27 additions & 14 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
module github.com/vx3r/wg-gen-web

go 1.16
go 1.21

toolchain go1.22.1

require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.10.0
github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e
github.com/gin-contrib/cors v1.3.1
github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.7.7
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/joho/godotenv v1.4.0
github.com/json-iterator/go v1.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/satori/go.uuid v1.2.0
github.com/sirupsen/logrus v1.8.1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/ugorji/go v1.2.6 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/oauth2 v0.13.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)

require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Loading