diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index e1de9143..7ad19d81 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -319,7 +319,7 @@ jobs: dockerfile: Dockerfile.base tags: "base" - - name: Latest/Version Docker Image + - name: Dev Docker Image uses: elgohr/Publish-Docker-Github-Action@master env: VERSION: ${{ env.VERSION }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 0d3c67c0..7602ac04 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,10 +5,9 @@ on: - '*' # matches every branch - '*/*' # matches every branch containing a single '/' - '!master' # excludes master - - '!dev' # excludes dev pull_request: branches: - - master + - dev jobs: compile: diff --git a/frontend/src/forms/OAuth.vue b/frontend/src/forms/OAuth.vue index 46933705..ec210e2e 100644 --- a/frontend/src/forms/OAuth.vue +++ b/frontend/src/forms/OAuth.vue @@ -46,6 +46,18 @@ +
+ +
+ +
+
+
+ +
+ +
+
@@ -172,6 +184,8 @@ oauth: { gh_client_id: "", gh_client_secret: "", + gh_users: "", + gh_orgs: "", google_client_id: "", google_client_secret: "", oauth_domains: "", diff --git a/handlers/oauth.go b/handlers/oauth.go index 14f9bc40..3a7f7149 100644 --- a/handlers/oauth.go +++ b/handlers/oauth.go @@ -1,18 +1,12 @@ package handlers import ( - "encoding/json" "fmt" "github.com/gorilla/mux" "github.com/statping/statping/types/core" "github.com/statping/statping/types/null" "github.com/statping/statping/types/users" - "github.com/statping/statping/utils" - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" - "golang.org/x/oauth2/slack" "net/http" - "time" ) type oAuth struct { @@ -63,87 +57,3 @@ func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) { //returnJson(user, w, r) http.Redirect(w, r, core.App.Domain+"/dashboard", http.StatusPermanentRedirect) } - -func googleOAuth(r *http.Request) (*oAuth, error) { - c := core.App - code := r.URL.Query().Get("code") - - config := &oauth2.Config{ - ClientID: c.OAuth.GoogleClientID, - ClientSecret: c.OAuth.GoogleClientSecret, - Endpoint: google.Endpoint, - } - - gg, err := config.Exchange(r.Context(), code) - if err != nil { - return nil, err - } - - return &oAuth{ - Token: gg.AccessToken, - RefreshToken: gg.RefreshToken, - Valid: gg.Valid(), - }, nil -} - -func slackOAuth(r *http.Request) (*oAuth, error) { - c := core.App - code := r.URL.Query().Get("code") - - config := &oauth2.Config{ - ClientID: c.OAuth.SlackClientID, - ClientSecret: c.OAuth.SlackClientSecret, - Endpoint: slack.Endpoint, - RedirectURL: c.Domain + basePath + "oauth/slack", - Scopes: []string{"identity.basic"}, - } - - gg, err := config.Exchange(r.Context(), code) - if err != nil { - return nil, err - } - - oauther := &oAuth{ - Token: gg.AccessToken, - RefreshToken: gg.RefreshToken, - Valid: gg.Valid(), - Type: gg.Type(), - } - - return oauther.slackIdentity() -} - -// slackIdentity will query the Slack API to fetch the users ID, username, and email address. -func (a *oAuth) slackIdentity() (*oAuth, error) { - url := fmt.Sprintf("https://slack.com/api/users.identity?token=%s", a.Token) - out, resp, err := utils.HttpRequest(url, "GET", "application/x-www-form-urlencoded", nil, nil, 10*time.Second, true, nil) - if err != nil { - return a, err - } - defer resp.Body.Close() - - var i *slackIdentity - if err := json.Unmarshal(out, &i); err != nil { - return a, err - } - a.Email = i.User.Email - a.ID = i.User.ID - a.Username = i.User.Name - return a, nil -} - -type slackIdentity struct { - Ok bool `json:"ok"` - User struct { - Name string `json:"name"` - ID string `json:"id"` - Email string `json:"email"` - } `json:"user"` - Team struct { - ID string `json:"id"` - } `json:"team"` -} - -func secureToken(w http.ResponseWriter, r *http.Request) { - -} diff --git a/handlers/oauth_github.go b/handlers/oauth_github.go index fb6a848a..9a9ac856 100644 --- a/handlers/oauth_github.go +++ b/handlers/oauth_github.go @@ -3,6 +3,7 @@ package handlers import ( "encoding/json" "github.com/statping/statping/types/core" + "github.com/statping/statping/types/errors" "github.com/statping/statping/utils" "golang.org/x/oauth2" "golang.org/x/oauth2/github" @@ -26,21 +27,20 @@ func githubOAuth(r *http.Request) (*oAuth, error) { return nil, err } - headers := []string{ - "Accept=application/vnd.github.machine-man-preview+json", - "Authorization=token " + gg.AccessToken, - } - - resp, _, err := utils.HttpRequest("https://api.github.com/user", "GET", nil, headers, nil, 10*time.Second, true, nil) + user, err := returnGithubUser(gg.AccessToken) if err != nil { return nil, err } - var user githubUser - if err := json.Unmarshal(resp, &user); err != nil { + orgs, err := returnGithubOrganizations(gg.AccessToken, user.Login) + if err != nil { return nil, err } + if allowed := validateGithub(user, orgs); !allowed { + return nil, errors.New("github user is not allowed to login") + } + return &oAuth{ Token: gg.AccessToken, RefreshToken: gg.RefreshToken, @@ -51,6 +51,80 @@ func githubOAuth(r *http.Request) (*oAuth, error) { }, nil } +func returnGithubUser(token string) (githubUser, error) { + headers := []string{ + "Accept=application/vnd.github.machine-man-preview+json", + "Authorization=token " + token, + } + resp, _, err := utils.HttpRequest("https://api.github.com/user", "GET", nil, headers, nil, 10*time.Second, true, nil) + if err != nil { + return githubUser{}, err + } + var user githubUser + if err := json.Unmarshal(resp, &user); err != nil { + return githubUser{}, err + } + return user, nil +} + +func returnGithubOrganizations(token, username string) ([]githubOrgs, error) { + headers := []string{ + "Accept=application/vnd.github.machine-man-preview+json", + "Authorization=token " + token, + } + resp, _, err := utils.HttpRequest("https://api.github.com/users/"+username+"/orgs", "GET", nil, headers, nil, 10*time.Second, true, nil) + if err != nil { + return nil, err + } + var orgs []githubOrgs + if err := json.Unmarshal(resp, &orgs); err != nil { + return nil, err + } + return orgs, nil +} + +func validateGithub(ghUser githubUser, orgs []githubOrgs) bool { + auth := core.App.OAuth + if auth.GithubUsers == "" && auth.GithubOrgs == "" { + return true + } + + if auth.GithubUsers != "" { + users := strings.Split(auth.GithubUsers, ",") + for _, u := range users { + if strings.ToLower(ghUser.Login) == strings.ToLower(u) { + return true + } + } + } + if auth.GithubOrgs != "" { + orgsAllowed := strings.Split(auth.GithubOrgs, ",") + for _, o := range orgsAllowed { + for _, org := range orgs { + if strings.ToLower(o) == strings.ToLower(org.Login) { + return true + } + } + } + } + return false +} + +type githubOrgs struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + URL string `json:"url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + HooksURL string `json:"hooks_url"` + IssuesURL string `json:"issues_url"` + MembersURL string `json:"members_url"` + PublicMembersURL string `json:"public_members_url"` + AvatarURL string `json:"avatar_url"` + Description string `json:"description"` +} + type githubUser struct { Login string `json:"login"` ID int `json:"id"` diff --git a/handlers/oauth_google.go b/handlers/oauth_google.go new file mode 100644 index 00000000..fb231716 --- /dev/null +++ b/handlers/oauth_google.go @@ -0,0 +1,30 @@ +package handlers + +import ( + "github.com/statping/statping/types/core" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "net/http" +) + +func googleOAuth(r *http.Request) (*oAuth, error) { + c := core.App + code := r.URL.Query().Get("code") + + config := &oauth2.Config{ + ClientID: c.OAuth.GoogleClientID, + ClientSecret: c.OAuth.GoogleClientSecret, + Endpoint: google.Endpoint, + } + + gg, err := config.Exchange(r.Context(), code) + if err != nil { + return nil, err + } + + return &oAuth{ + Token: gg.AccessToken, + RefreshToken: gg.RefreshToken, + Valid: gg.Valid(), + }, nil +} diff --git a/handlers/oauth_slack.go b/handlers/oauth_slack.go new file mode 100644 index 00000000..857065bf --- /dev/null +++ b/handlers/oauth_slack.go @@ -0,0 +1,79 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "github.com/statping/statping/types/core" + "github.com/statping/statping/types/errors" + "github.com/statping/statping/utils" + "golang.org/x/oauth2" + "golang.org/x/oauth2/slack" + "net/http" + "time" +) + +func slackOAuth(r *http.Request) (*oAuth, error) { + c := core.App + code := r.URL.Query().Get("code") + + config := &oauth2.Config{ + ClientID: c.OAuth.SlackClientID, + ClientSecret: c.OAuth.SlackClientSecret, + Endpoint: slack.Endpoint, + RedirectURL: c.Domain + basePath + "oauth/slack", + Scopes: []string{"identity.basic"}, + } + + gg, err := config.Exchange(r.Context(), code) + if err != nil { + return nil, err + } + + oauther := &oAuth{ + Token: gg.AccessToken, + RefreshToken: gg.RefreshToken, + Valid: gg.Valid(), + Type: gg.Type(), + } + + identity, err := returnSlackIdentity(gg.AccessToken) + if err != nil { + return nil, err + } + + if !identity.Ok { + return nil, errors.New("slack identity is invalid") + } + + oauther.Username = identity.User.Name + oauther.Email = identity.User.Email + + return oauther, nil +} + +// slackIdentity will query the Slack API to fetch the users ID, username, and email address. +func returnSlackIdentity(token string) (slackIdentity, error) { + url := fmt.Sprintf("https://slack.com/api/users.identity?token=%s", token) + out, _, err := utils.HttpRequest(url, "GET", "application/x-www-form-urlencoded", nil, nil, 10*time.Second, true, nil) + if err != nil { + return slackIdentity{}, err + } + + var i slackIdentity + if err := json.Unmarshal(out, &i); err != nil { + return slackIdentity{}, err + } + return i, nil +} + +type slackIdentity struct { + Ok bool `json:"ok"` + User struct { + Name string `json:"name"` + ID string `json:"id"` + Email string `json:"email"` + } `json:"user"` + Team struct { + ID string `json:"id"` + } `json:"team"` +} diff --git a/types/core/struct.go b/types/core/struct.go index 6f52a482..88d51a13 100644 --- a/types/core/struct.go +++ b/types/core/struct.go @@ -47,11 +47,14 @@ type OAuth struct { Providers string `gorm:"column:oauth_providers;" json:"oauth_providers"` GithubClientID string `gorm:"column:gh_client_id" json:"gh_client_id"` GithubClientSecret string `gorm:"column:gh_client_secret" json:"gh_client_secret" scope:"admin"` + GithubUsers string `gorm:"column:gh_users" json:"gh_users" scope:"admin"` + GithubOrgs string `gorm:"column:gh_orgs" json:"gh_orgs" scope:"admin"` GoogleClientID string `gorm:"column:google_client_id" json:"google_client_id"` GoogleClientSecret string `gorm:"column:google_client_secret" json:"google_client_secret" scope:"admin"` + GoogleUsers string `gorm:"column:google_users" json:"google_users" scope:"admin"` SlackClientID string `gorm:"column:slack_client_id" json:"slack_client_id"` SlackClientSecret string `gorm:"column:slack_client_secret" json:"slack_client_secret" scope:"admin"` - SlackTeam string `gorm:"column:slack_team" json:"slack_team"` + SlackTeam string `gorm:"column:slack_team" json:"slack_team" scope:"admin"` } // AllNotifiers contains all the Notifiers loaded