mirror of https://github.com/statping/statping
added github oauth username and organizations for authentication
parent
cbb80bb55a
commit
ea23865494
|
@ -319,7 +319,7 @@ jobs:
|
||||||
dockerfile: Dockerfile.base
|
dockerfile: Dockerfile.base
|
||||||
tags: "base"
|
tags: "base"
|
||||||
|
|
||||||
- name: Latest/Version Docker Image
|
- name: Dev Docker Image
|
||||||
uses: elgohr/Publish-Docker-Github-Action@master
|
uses: elgohr/Publish-Docker-Github-Action@master
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ env.VERSION }}
|
VERSION: ${{ env.VERSION }}
|
||||||
|
|
|
@ -5,10 +5,9 @@ on:
|
||||||
- '*' # matches every branch
|
- '*' # matches every branch
|
||||||
- '*/*' # matches every branch containing a single '/'
|
- '*/*' # matches every branch containing a single '/'
|
||||||
- '!master' # excludes master
|
- '!master' # excludes master
|
||||||
- '!dev' # excludes dev
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- dev
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
compile:
|
compile:
|
||||||
|
|
|
@ -46,6 +46,18 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="form-group row">
|
||||||
<label for="gh_callback" class="col-sm-4 col-form-label">Callback URL</label>
|
<label for="gh_callback" class="col-sm-4 col-form-label">Callback URL</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
|
@ -172,6 +184,8 @@
|
||||||
oauth: {
|
oauth: {
|
||||||
gh_client_id: "",
|
gh_client_id: "",
|
||||||
gh_client_secret: "",
|
gh_client_secret: "",
|
||||||
|
gh_users: "",
|
||||||
|
gh_orgs: "",
|
||||||
google_client_id: "",
|
google_client_id: "",
|
||||||
google_client_secret: "",
|
google_client_secret: "",
|
||||||
oauth_domains: "",
|
oauth_domains: "",
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/statping/statping/types/core"
|
"github.com/statping/statping/types/core"
|
||||||
"github.com/statping/statping/types/null"
|
"github.com/statping/statping/types/null"
|
||||||
"github.com/statping/statping/types/users"
|
"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"
|
"net/http"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type oAuth struct {
|
type oAuth struct {
|
||||||
|
@ -63,87 +57,3 @@ func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) {
|
||||||
//returnJson(user, w, r)
|
//returnJson(user, w, r)
|
||||||
http.Redirect(w, r, core.App.Domain+"/dashboard", http.StatusPermanentRedirect)
|
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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/statping/statping/types/core"
|
"github.com/statping/statping/types/core"
|
||||||
|
"github.com/statping/statping/types/errors"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/github"
|
"golang.org/x/oauth2/github"
|
||||||
|
@ -26,21 +27,20 @@ func githubOAuth(r *http.Request) (*oAuth, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{
|
user, err := returnGithubUser(gg.AccessToken)
|
||||||
"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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var user githubUser
|
orgs, err := returnGithubOrganizations(gg.AccessToken, user.Login)
|
||||||
if err := json.Unmarshal(resp, &user); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if allowed := validateGithub(user, orgs); !allowed {
|
||||||
|
return nil, errors.New("github user is not allowed to login")
|
||||||
|
}
|
||||||
|
|
||||||
return &oAuth{
|
return &oAuth{
|
||||||
Token: gg.AccessToken,
|
Token: gg.AccessToken,
|
||||||
RefreshToken: gg.RefreshToken,
|
RefreshToken: gg.RefreshToken,
|
||||||
|
@ -51,6 +51,80 @@ func githubOAuth(r *http.Request) (*oAuth, error) {
|
||||||
}, nil
|
}, 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 {
|
type githubUser struct {
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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"`
|
||||||
|
}
|
|
@ -47,11 +47,14 @@ type OAuth struct {
|
||||||
Providers string `gorm:"column:oauth_providers;" json:"oauth_providers"`
|
Providers string `gorm:"column:oauth_providers;" json:"oauth_providers"`
|
||||||
GithubClientID string `gorm:"column:gh_client_id" json:"gh_client_id"`
|
GithubClientID string `gorm:"column:gh_client_id" json:"gh_client_id"`
|
||||||
GithubClientSecret string `gorm:"column:gh_client_secret" json:"gh_client_secret" scope:"admin"`
|
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"`
|
GoogleClientID string `gorm:"column:google_client_id" json:"google_client_id"`
|
||||||
GoogleClientSecret string `gorm:"column:google_client_secret" json:"google_client_secret" scope:"admin"`
|
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"`
|
SlackClientID string `gorm:"column:slack_client_id" json:"slack_client_id"`
|
||||||
SlackClientSecret string `gorm:"column:slack_client_secret" json:"slack_client_secret" scope:"admin"`
|
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
|
// AllNotifiers contains all the Notifiers loaded
|
||||||
|
|
Loading…
Reference in New Issue