added github oauth username and organizations for authentication

pull/702/head
hunterlong 2020-06-25 18:37:57 -07:00
parent cbb80bb55a
commit ea23865494
8 changed files with 211 additions and 102 deletions

View File

@ -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 }}

View File

@ -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:

View File

@ -46,6 +46,18 @@
</span>
</div>
</div>
<div class="form-group row">
<label for="github_secret" class="col-sm-4 col-form-label">Github Restrict Users</label>
<div class="col-sm-8">
<input v-model="oauth.gh_users" type="text" class="form-control" id="github_users">
</div>
</div>
<div class="form-group row">
<label for="github_secret" class="col-sm-4 col-form-label">Github Restrict Organizations</label>
<div class="col-sm-8">
<input v-model="oauth.gh_orgs" type="text" class="form-control" id="github_orgs">
</div>
</div>
<div class="form-group row">
<label for="gh_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
@ -172,6 +184,8 @@
oauth: {
gh_client_id: "",
gh_client_secret: "",
gh_users: "",
gh_orgs: "",
google_client_id: "",
google_client_secret: "",
oauth_domains: "",

View File

@ -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) {
}

View File

@ -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"`

30
handlers/oauth_google.go Normal file
View File

@ -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
}

79
handlers/oauth_slack.go Normal file
View File

@ -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"`
}

View File

@ -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