From 202695ba20979c69bd6cb294d2798a3c61e594ae Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 25 Jun 2020 21:08:12 -0700 Subject: [PATCH] oauth Google fixes, google oauth restrict users based on domain and/or email address, notifiers no dereference pointer for services and failure --- CHANGELOG.md | 4 ++ frontend/src/forms/OAuth.vue | 66 ++++++++++++++----------- handlers/oauth.go | 13 ++--- handlers/oauth_github.go | 21 ++++---- handlers/oauth_google.go | 78 +++++++++++++++++++++++++++--- handlers/oauth_slack.go | 48 +++++++++++++----- notifiers/command.go | 6 +-- notifiers/command_test.go | 7 +++ notifiers/discord.go | 6 +-- notifiers/discord_test.go | 7 +++ notifiers/email.go | 10 ++-- notifiers/email_test.go | 7 +++ notifiers/line_notify.go | 6 +-- notifiers/mobile.go | 17 +++---- notifiers/mobile_test.go | 7 +++ notifiers/notifiers.go | 10 ++-- notifiers/pushover.go | 8 +-- notifiers/pushover_test.go | 9 ++++ notifiers/slack.go | 8 +-- notifiers/slack_test.go | 7 +++ notifiers/statping_emailer.go | 24 ++++----- notifiers/statping_emailer_test.go | 7 +++ notifiers/telegram.go | 6 +-- notifiers/telegram_test.go | 7 +++ notifiers/twilio.go | 6 +-- notifiers/twilio_test.go | 7 +++ notifiers/webhook.go | 6 +-- notifiers/webhook_test.go | 7 +++ types/core/samples.go | 16 ++++++ types/core/struct.go | 2 +- types/failures/samples.go | 4 +- types/notifications/methods.go | 7 ++- types/notifier/interface.go | 8 +-- types/services/failures.go | 9 ++-- types/services/methods.go | 12 ++--- types/services/notifier.go | 10 ++-- types/services/routine.go | 4 +- types/services/samples.go | 4 +- 38 files changed, 337 insertions(+), 154 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ced58e..1309bac1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Added error if Theme Editor returns an error from API - Added Pushover priority and sounds - Added HTTP headers for outgoing requests (includes User-Agent=Statping and Statping-Version=0.90.55) +- Fixed Google oAuth handling +- Added Google oAuth email/domain user restrictions +- Modified notifiers to use dereferenced services and failures +- Added core.Example() function for testing # 0.90.55 (06-18-2020) - Added 404 page diff --git a/frontend/src/forms/OAuth.vue b/frontend/src/forms/OAuth.vue index ec210e2e..acaff1d7 100644 --- a/frontend/src/forms/OAuth.vue +++ b/frontend/src/forms/OAuth.vue @@ -4,20 +4,15 @@
Internal Login
- -
+ +
- + + Use email/password Authentication
-
- -
- -
-
@@ -37,6 +32,20 @@
+
+ +
+ + Optional comma delimited list of usernames +
+
+
+ +
+ + Optional comma delimited list of Github Organizations +
+
@@ -46,18 +55,6 @@
-
- -
- -
-
-
- -
- -
-
@@ -74,7 +71,7 @@
Google Settings
- Go to OAuth Consent Screen on Google Console to create a new OAuth application. + Go to OAuth Consent Screen on Google Console to create a new "Web Application" OAuth application.
@@ -88,6 +85,13 @@
+
+ +
+ + Optional comma delimited list of emails and/or domains +
+
@@ -113,7 +117,7 @@
Slack Settings
- Go to OAuth Consent Screen on Google Console to create a new OAuth application. + Go to Slack Apps and create a new Application.
@@ -128,10 +132,17 @@
- +
- Optional + Optional Slack Team ID +
+
+
+ +
+ + Optional comma delimited list of email addresses
@@ -188,11 +199,12 @@ gh_orgs: "", google_client_id: "", google_client_secret: "", - oauth_domains: "", + google_users: "", oauth_providers: "", slack_client_id: "", slack_client_secret: "", - slack_team: "" + slack_team: "", + slack_users: "" } } }, diff --git a/handlers/oauth.go b/handlers/oauth.go index 3a7f7149..ba27d012 100644 --- a/handlers/oauth.go +++ b/handlers/oauth.go @@ -6,17 +6,14 @@ import ( "github.com/statping/statping/types/core" "github.com/statping/statping/types/null" "github.com/statping/statping/types/users" + "golang.org/x/oauth2" "net/http" ) type oAuth struct { - ID string - Email string - Username string - Token string - RefreshToken string - Valid bool - Type string + Email string + Username string + *oauth2.Token } func oauthHandler(w http.ResponseWriter, r *http.Request) { @@ -51,9 +48,7 @@ func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) { Admin: null.NewNullBool(true), } log.Infoln(fmt.Sprintf("OAuth %s User %s logged in from IP %s", oauth.Type, oauth.Email, r.RemoteAddr)) - setJwtToken(user, w) - //returnJson(user, w, r) http.Redirect(w, r, core.App.Domain+"/dashboard", http.StatusPermanentRedirect) } diff --git a/handlers/oauth_github.go b/handlers/oauth_github.go index 9a9ac856..d35d3117 100644 --- a/handlers/oauth_github.go +++ b/handlers/oauth_github.go @@ -13,12 +13,12 @@ import ( ) func githubOAuth(r *http.Request) (*oAuth, error) { - c := *core.App + auth := core.App.OAuth code := r.URL.Query().Get("code") config := &oauth2.Config{ - ClientID: c.OAuth.GithubClientID, - ClientSecret: c.OAuth.GithubClientSecret, + ClientID: auth.GithubClientID, + ClientSecret: auth.GithubClientSecret, Endpoint: github.Endpoint, } @@ -27,6 +27,10 @@ func githubOAuth(r *http.Request) (*oAuth, error) { return nil, err } + if !gg.Valid() { + return nil, errors.New("oauth token is not valid") + } + user, err := returnGithubUser(gg.AccessToken) if err != nil { return nil, err @@ -37,17 +41,14 @@ func githubOAuth(r *http.Request) (*oAuth, error) { return nil, err } - if allowed := validateGithub(user, orgs); !allowed { + if !validateGithub(user, orgs) { return nil, errors.New("github user is not allowed to login") } return &oAuth{ - Token: gg.AccessToken, - RefreshToken: gg.RefreshToken, - Valid: gg.Valid(), - Username: strings.ToLower(user.Name), - Email: strings.ToLower(user.Email), - Type: "github", + Token: gg, + Username: strings.ToLower(user.Name), + Email: strings.ToLower(user.Email), }, nil } diff --git a/handlers/oauth_google.go b/handlers/oauth_google.go index fb231716..0f866fcf 100644 --- a/handlers/oauth_google.go +++ b/handlers/oauth_google.go @@ -1,20 +1,26 @@ 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/google" "net/http" + "strings" + "time" ) func googleOAuth(r *http.Request) (*oAuth, error) { - c := core.App + auth := core.App.OAuth code := r.URL.Query().Get("code") config := &oauth2.Config{ - ClientID: c.OAuth.GoogleClientID, - ClientSecret: c.OAuth.GoogleClientSecret, + ClientID: auth.GoogleClientID, + ClientSecret: auth.GoogleClientSecret, Endpoint: google.Endpoint, + RedirectURL: core.App.Domain + "/oauth/google", } gg, err := config.Exchange(r.Context(), code) @@ -22,9 +28,69 @@ func googleOAuth(r *http.Request) (*oAuth, error) { return nil, err } + if !gg.Valid() { + return nil, errors.New("oauth token is not valid") + } + + info, err := returnGoogleInfo(gg.AccessToken) + if err != nil { + return nil, err + } + + if !validateGoogle(info) { + return nil, errors.New("google user is not allowed to login") + } + return &oAuth{ - Token: gg.AccessToken, - RefreshToken: gg.RefreshToken, - Valid: gg.Valid(), + Token: gg, + Username: info.Name, + Email: info.Email, }, nil } + +func validateGoogle(info googleUserInfo) bool { + auth := core.App.OAuth + if auth.GoogleUsers == "" { + return true + } + + if auth.GoogleUsers != "" { + users := strings.Split(auth.GoogleUsers, ",") + for _, u := range users { + if strings.ToLower(info.Email) == strings.ToLower(u) { + return true + } + if strings.ToLower(info.Hd) == strings.ToLower(u) { + return true + } + } + } + + return false +} + +func returnGoogleInfo(token string) (googleUserInfo, error) { + resp, _, err := utils.HttpRequest("https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token="+token, "GET", nil, nil, nil, 10*time.Second, true, nil) + if err != nil { + return googleUserInfo{}, err + } + var user googleUserInfo + if err := json.Unmarshal(resp, &user); err != nil { + return googleUserInfo{}, err + } + return user, nil +} + +type googleUserInfo struct { + ID string `json:"id"` + Email string `json:"email"` + VerifiedEmail bool `json:"verified_email"` + Name string `json:"name"` + GivenName string `json:"given_name"` + FamilyName string `json:"family_name"` + Link string `json:"link"` + Picture string `json:"picture"` + Gender string `json:"gender"` + Locale string `json:"locale"` + Hd string `json:"hd"` +} diff --git a/handlers/oauth_slack.go b/handlers/oauth_slack.go index 857065bf..bb482f88 100644 --- a/handlers/oauth_slack.go +++ b/handlers/oauth_slack.go @@ -9,18 +9,19 @@ import ( "golang.org/x/oauth2" "golang.org/x/oauth2/slack" "net/http" + "strings" "time" ) func slackOAuth(r *http.Request) (*oAuth, error) { - c := core.App + auth := core.App.OAuth code := r.URL.Query().Get("code") config := &oauth2.Config{ - ClientID: c.OAuth.SlackClientID, - ClientSecret: c.OAuth.SlackClientSecret, + ClientID: auth.SlackClientID, + ClientSecret: auth.SlackClientSecret, Endpoint: slack.Endpoint, - RedirectURL: c.Domain + basePath + "oauth/slack", + RedirectURL: core.App.Domain + basePath + "oauth/slack", Scopes: []string{"identity.basic"}, } @@ -29,11 +30,8 @@ func slackOAuth(r *http.Request) (*oAuth, error) { return nil, err } - oauther := &oAuth{ - Token: gg.AccessToken, - RefreshToken: gg.RefreshToken, - Valid: gg.Valid(), - Type: gg.Type(), + if !gg.Valid() { + return nil, errors.New("oauth token is not valid") } identity, err := returnSlackIdentity(gg.AccessToken) @@ -45,10 +43,36 @@ func slackOAuth(r *http.Request) (*oAuth, error) { return nil, errors.New("slack identity is invalid") } - oauther.Username = identity.User.Name - oauther.Email = identity.User.Email + if !validateSlack(identity) { + return nil, errors.New("slack user is not whitelisted") + } - return oauther, nil + return &oAuth{ + Token: gg, + Username: strings.ToLower(identity.User.Name), + Email: strings.ToLower(identity.User.Email), + }, nil +} + +func validateSlack(id slackIdentity) bool { + auth := core.App.OAuth + if auth.SlackUsers == "" { + return true + } + + if auth.SlackUsers != "" { + users := strings.Split(auth.SlackUsers, ",") + for _, u := range users { + if strings.ToLower(u) == strings.ToLower(id.User.Email) { + return true + } + if strings.ToLower(u) == strings.ToLower(id.User.Name) { + return true + } + } + } + + return false } // slackIdentity will query the Slack API to fetch the users ID, username, and email address. diff --git a/notifiers/command.go b/notifiers/command.go index 6e1e5c68..47a6ee4f 100644 --- a/notifiers/command.go +++ b/notifiers/command.go @@ -49,14 +49,14 @@ func runCommand(app string, cmd ...string) (string, string, error) { } // OnSuccess for commandLine will trigger successful service -func (c *commandLine) OnSuccess(s *services.Service) (string, error) { - tmpl := ReplaceVars(c.SuccessData, s, nil) +func (c *commandLine) OnSuccess(s services.Service) (string, error) { + tmpl := ReplaceVars(c.SuccessData, s, failures.Failure{}) out, _, err := runCommand(c.Host, tmpl) return out, err } // OnFailure for commandLine will trigger failing service -func (c *commandLine) OnFailure(s *services.Service, f *failures.Failure) (string, error) { +func (c *commandLine) OnFailure(s services.Service, f failures.Failure) (string, error) { tmpl := ReplaceVars(c.FailureData, s, f) _, ouerr, err := runCommand(c.Host, tmpl) return ouerr, err diff --git a/notifiers/command_test.go b/notifiers/command_test.go index 4827e73f..b424fb65 100644 --- a/notifiers/command_test.go +++ b/notifiers/command_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -21,6 +22,7 @@ func TestCommandNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() t.Run("Load Command", func(t *testing.T) { Command.Host = "/bin/echo" @@ -40,6 +42,11 @@ func TestCommandNotifier(t *testing.T) { assert.True(t, Command.CanSend()) }) + t.Run("Command OnSave", func(t *testing.T) { + _, err := Command.OnSave() + assert.Nil(t, err) + }) + t.Run("Command OnFailure", func(t *testing.T) { _, err := Command.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/discord.go b/notifiers/discord.go index 1e6b37f5..416ab57d 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -51,14 +51,14 @@ func (d *discord) Select() *notifications.Notification { } // OnFailure will trigger failing service -func (d *discord) OnFailure(s *services.Service, f *failures.Failure) (string, error) { +func (d *discord) OnFailure(s services.Service, f failures.Failure) (string, error) { out, err := d.sendRequest(ReplaceVars(d.FailureData, s, f)) return out, err } // OnSuccess will trigger successful service -func (d *discord) OnSuccess(s *services.Service) (string, error) { - out, err := d.sendRequest(ReplaceVars(d.SuccessData, s, nil)) +func (d *discord) OnSuccess(s services.Service) (string, error) { + out, err := d.sendRequest(ReplaceVars(d.SuccessData, s, failures.Failure{})) return out, err } diff --git a/notifiers/discord_test.go b/notifiers/discord_test.go index e1648ddc..7d33b3b1 100644 --- a/notifiers/discord_test.go +++ b/notifiers/discord_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -26,6 +27,7 @@ func TestDiscordNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() if DISCORD_URL == "" { t.Log("discord notifier testing skipped, missing DISCORD_URL environment variable") @@ -47,6 +49,11 @@ func TestDiscordNotifier(t *testing.T) { assert.True(t, Discorder.CanSend()) }) + t.Run("discord Notifier Tester OnSave", func(t *testing.T) { + _, err := Discorder.OnSave() + assert.Nil(t, err) + }) + t.Run("discord OnFailure", func(t *testing.T) { _, err := Discorder.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/email.go b/notifiers/email.go index 37c1a291..d3ec0461 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -86,7 +86,7 @@ type emailOutgoing struct { } // OnFailure will trigger failing service -func (e *emailer) OnFailure(s *services.Service, f *failures.Failure) (string, error) { +func (e *emailer) OnFailure(s services.Service, f failures.Failure) (string, error) { subject := fmt.Sprintf("Service %s is Offline", s.Name) tmpl := renderEmail(s, f) email := &emailOutgoing{ @@ -99,9 +99,9 @@ func (e *emailer) OnFailure(s *services.Service, f *failures.Failure) (string, e } // OnSuccess will trigger successful service -func (e *emailer) OnSuccess(s *services.Service) (string, error) { +func (e *emailer) OnSuccess(s services.Service) (string, error) { subject := fmt.Sprintf("Service %s is Back Online", s.Name) - tmpl := renderEmail(s, nil) + tmpl := renderEmail(s, failures.Failure{}) email := &emailOutgoing{ To: e.Var2, Subject: subject, @@ -111,7 +111,7 @@ func (e *emailer) OnSuccess(s *services.Service) (string, error) { return tmpl, e.dialSend(email) } -func renderEmail(s *services.Service, f *failures.Failure) string { +func renderEmail(s services.Service, f failures.Failure) string { wr := bytes.NewBuffer(nil) tmpl := template.New("email") tmpl, err := tmpl.Parse(emailBase) @@ -121,7 +121,7 @@ func renderEmail(s *services.Service, f *failures.Failure) string { } data := replacer{ - Core: core.App, + Core: *core.App, Service: s, Failure: f, Custom: nil, diff --git a/notifiers/email_test.go b/notifiers/email_test.go index 8c00cd3d..cbcec63e 100644 --- a/notifiers/email_test.go +++ b/notifiers/email_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -37,6 +38,7 @@ func TestEmailNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() if EMAIL_HOST == "" || EMAIL_USER == "" || EMAIL_PASS == "" { t.Log("email notifier testing skipped, missing EMAIL_ environment variables") @@ -63,6 +65,11 @@ func TestEmailNotifier(t *testing.T) { assert.True(t, ok) }) + t.Run("email OnSave", func(t *testing.T) { + _, err := email.OnSave() + assert.Nil(t, err) + }) + t.Run("email OnFailure", func(t *testing.T) { _, err := email.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index b432517d..37d0908d 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -52,14 +52,14 @@ func (l *lineNotifier) sendMessage(message string) (string, error) { } // OnFailure will trigger failing service -func (l *lineNotifier) OnFailure(s *services.Service, f *failures.Failure) (string, error) { - msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name) +func (l *lineNotifier) OnFailure(s services.Service, f failures.Failure) (string, error) { + msg := fmt.Sprintf("Your service '%v' is currently offline! %s", s.Name, f.Issue) out, err := l.sendMessage(msg) return out, err } // OnSuccess will trigger successful service -func (l *lineNotifier) OnSuccess(s *services.Service) (string, error) { +func (l *lineNotifier) OnSuccess(s services.Service) (string, error) { msg := fmt.Sprintf("Service %s is online!", s.Name) out, err := l.sendMessage(msg) return out, err diff --git a/notifiers/mobile.go b/notifiers/mobile.go index ab05d80f..d3664642 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -47,19 +47,14 @@ var Mobile = &mobilePush{¬ifications.Notification{ }}}, } -func dataJson(s *services.Service, f *failures.Failure) map[string]interface{} { +func dataJson(s services.Service, f failures.Failure) map[string]interface{} { serviceId := "0" - if s != nil { - serviceId = utils.ToString(s.Id) - } + serviceId = utils.ToString(s.Id) online := "online" if !s.Online { online = "offline" } - issue := "" - if f != nil { - issue = f.Issue - } + issue := f.Issue link := fmt.Sprintf("statping://service?id=%v", serviceId) out := map[string]interface{}{ "status": online, @@ -71,7 +66,7 @@ func dataJson(s *services.Service, f *failures.Failure) map[string]interface{} { } // OnFailure will trigger failing service -func (m *mobilePush) OnFailure(s *services.Service, f *failures.Failure) (string, error) { +func (m *mobilePush) OnFailure(s services.Service, f failures.Failure) (string, error) { data := dataJson(s, f) msg := &pushArray{ Message: fmt.Sprintf("Your service '%v' is currently failing! Reason: %v", s.Name, f.Issue), @@ -82,8 +77,8 @@ func (m *mobilePush) OnFailure(s *services.Service, f *failures.Failure) (string } // OnSuccess will trigger successful service -func (m *mobilePush) OnSuccess(s *services.Service) (string, error) { - data := dataJson(s, nil) +func (m *mobilePush) OnSuccess(s services.Service) (string, error) { + data := dataJson(s, failures.Failure{}) msg := &pushArray{ Message: "Service is Online!", Title: "Service Online", diff --git a/notifiers/mobile_test.go b/notifiers/mobile_test.go index ac358327..5268e1e9 100644 --- a/notifiers/mobile_test.go +++ b/notifiers/mobile_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -29,6 +30,7 @@ func TestMobileNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() Mobile.Var1 = mobileToken if mobileToken == "" { @@ -52,6 +54,11 @@ func TestMobileNotifier(t *testing.T) { assert.True(t, Mobile.CanSend()) }) + t.Run("Mobile OnSave", func(t *testing.T) { + _, err := Mobile.OnSave() + assert.Nil(t, err) + }) + t.Run("Mobile OnFailure", func(t *testing.T) { _, err := Mobile.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go index 4ba59fb2..d1a74a26 100644 --- a/notifiers/notifiers.go +++ b/notifiers/notifiers.go @@ -13,9 +13,9 @@ import ( var log = utils.Log.WithField("type", "notifier") type replacer struct { - Core *core.Core - Service *services.Service - Failure *failures.Failure + Core core.Core + Service services.Service + Failure failures.Failure Custom map[string]string } @@ -59,8 +59,8 @@ func Add(notifs ...services.ServiceNotifier) { } } -func ReplaceVars(input string, s *services.Service, f *failures.Failure) string { - return ReplaceTemplate(input, replacer{Service: s, Failure: f, Core: core.App}) +func ReplaceVars(input string, s services.Service, f failures.Failure) string { + return ReplaceTemplate(input, replacer{Service: s, Failure: f, Core: *core.App}) } var exampleFailure = &failures.Failure{ diff --git a/notifiers/pushover.go b/notifiers/pushover.go index 1f295e13..623eb0fd 100644 --- a/notifiers/pushover.go +++ b/notifiers/pushover.go @@ -81,7 +81,7 @@ func priority(val string) string { case "emergency": return "2" default: - return "1" + return "0" } } @@ -105,15 +105,15 @@ func (t *pushover) sendMessage(message string) (string, error) { } // OnFailure will trigger failing service -func (t *pushover) OnFailure(s *services.Service, f *failures.Failure) (string, error) { +func (t *pushover) OnFailure(s services.Service, f failures.Failure) (string, error) { message := ReplaceVars(t.FailureData, s, f) out, err := t.sendMessage(message) return out, err } // OnSuccess will trigger successful service -func (t *pushover) OnSuccess(s *services.Service) (string, error) { - message := ReplaceVars(t.SuccessData, s, nil) +func (t *pushover) OnSuccess(s services.Service) (string, error) { + message := ReplaceVars(t.SuccessData, s, failures.Failure{}) out, err := t.sendMessage(message) return out, err } diff --git a/notifiers/pushover_test.go b/notifiers/pushover_test.go index 58a52ddd..aea799c0 100644 --- a/notifiers/pushover_test.go +++ b/notifiers/pushover_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -28,6 +29,7 @@ func TestPushoverNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() if PUSHOVER_TOKEN == "" || PUSHOVER_API == "" { t.Log("Pushover notifier testing skipped, missing PUSHOVER_TOKEN and PUSHOVER_API environment variable") @@ -37,6 +39,8 @@ func TestPushoverNotifier(t *testing.T) { t.Run("Load Pushover", func(t *testing.T) { Pushover.ApiKey = PUSHOVER_TOKEN Pushover.ApiSecret = PUSHOVER_API + Pushover.Var1 = "Normal" + Pushover.Var2 = "vibrate" Pushover.Enabled = null.NewNullBool(true) Add(Pushover) @@ -50,6 +54,11 @@ func TestPushoverNotifier(t *testing.T) { assert.True(t, Pushover.CanSend()) }) + t.Run("Pushover OnSave", func(t *testing.T) { + _, err := Pushover.OnSave() + assert.Nil(t, err) + }) + t.Run("Pushover OnFailure", func(t *testing.T) { _, err := Pushover.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/slack.go b/notifiers/slack.go index 3c9eae3a..8b1bfdd7 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -61,7 +61,7 @@ func (s *slack) sendSlack(msg string) (string, error) { func (s *slack) OnTest() (string, error) { example := services.Example(true) - testMsg := ReplaceVars(s.SuccessData, example, nil) + testMsg := ReplaceVars(s.SuccessData, example, failures.Failure{}) contents, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(testMsg)), time.Duration(10*time.Second), true, nil) if err != nil { return "", err @@ -74,15 +74,15 @@ func (s *slack) OnTest() (string, error) { } // OnFailure will trigger failing service -func (s *slack) OnFailure(srv *services.Service, f *failures.Failure) (string, error) { +func (s *slack) OnFailure(srv services.Service, f failures.Failure) (string, error) { msg := ReplaceVars(s.FailureData, srv, f) out, err := s.sendSlack(msg) return out, err } // OnSuccess will trigger successful service -func (s *slack) OnSuccess(srv *services.Service) (string, error) { - msg := ReplaceVars(s.SuccessData, srv, nil) +func (s *slack) OnSuccess(srv services.Service) (string, error) { + msg := ReplaceVars(s.SuccessData, srv, failures.Failure{}) out, err := s.sendSlack(msg) return out, err } diff --git a/notifiers/slack_test.go b/notifiers/slack_test.go index ea21a093..cc05def5 100644 --- a/notifiers/slack_test.go +++ b/notifiers/slack_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -24,6 +25,7 @@ func TestSlackNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() SLACK_URL = utils.Params.GetString("SLACK_URL") slacker.Host = SLACK_URL @@ -48,6 +50,11 @@ func TestSlackNotifier(t *testing.T) { assert.True(t, ok) }) + t.Run("slack OnSave", func(t *testing.T) { + _, err := slacker.OnSave() + assert.Nil(t, err) + }) + t.Run("slack OnFailure", func(t *testing.T) { _, err := slacker.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/statping_emailer.go b/notifiers/statping_emailer.go index 444ee92c..81208f20 100644 --- a/notifiers/statping_emailer.go +++ b/notifiers/statping_emailer.go @@ -60,17 +60,17 @@ func (s *statpingEmailer) OnTest() (string, error) { } type statpingMail struct { - Email string `json:"email"` - Core *core.Core `json:"core,omitempty"` - Service *services.Service `json:"service,omitempty"` - Failure *failures.Failure `json:"failure,omitempty"` + Email string `json:"email"` + Core core.Core `json:"core,omitempty"` + Service services.Service `json:"service,omitempty"` + Failure failures.Failure `json:"failure,omitempty"` } // OnFailure will trigger failing service -func (s *statpingEmailer) OnFailure(srv *services.Service, f *failures.Failure) (string, error) { +func (s *statpingEmailer) OnFailure(srv services.Service, f failures.Failure) (string, error) { ee := statpingMail{ Email: s.Host, - Core: core.App, + Core: *core.App, Service: srv, Failure: f, } @@ -78,12 +78,12 @@ func (s *statpingEmailer) OnFailure(srv *services.Service, f *failures.Failure) } // OnSuccess will trigger successful service -func (s *statpingEmailer) OnSuccess(srv *services.Service) (string, error) { +func (s *statpingEmailer) OnSuccess(srv services.Service) (string, error) { ee := statpingMail{ Email: s.Host, - Core: core.App, + Core: *core.App, Service: srv, - Failure: nil, + Failure: failures.Failure{}, } return s.sendStatpingEmail(ee) } @@ -92,9 +92,9 @@ func (s *statpingEmailer) OnSuccess(srv *services.Service) (string, error) { func (s *statpingEmailer) OnSave() (string, error) { ee := statpingMail{ Email: s.Host, - Core: core.App, - Service: nil, - Failure: nil, + Core: *core.App, + Service: services.Service{}, + Failure: failures.Failure{}, } out, err := s.sendStatpingEmail(ee) log.Println("statping emailer response", out) diff --git a/notifiers/statping_emailer_test.go b/notifiers/statping_emailer_test.go index 7155cf24..76ebf05f 100644 --- a/notifiers/statping_emailer_test.go +++ b/notifiers/statping_emailer_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -24,6 +25,7 @@ func TestStatpingEmailerNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() testEmail = utils.Params.GetString("TEST_EMAIL") statpingMailer.Host = testEmail @@ -48,6 +50,11 @@ func TestStatpingEmailerNotifier(t *testing.T) { assert.True(t, ok) }) + t.Run("statping emailer OnSave", func(t *testing.T) { + _, err := statpingMailer.OnSave() + assert.Nil(t, err) + }) + t.Run("statping emailer OnFailure", func(t *testing.T) { _, err := statpingMailer.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/telegram.go b/notifiers/telegram.go index bd53cb29..e7ce4c0d 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -74,15 +74,15 @@ func (t *telegram) sendMessage(message string) (string, error) { } // OnFailure will trigger failing service -func (t *telegram) OnFailure(s *services.Service, f *failures.Failure) (string, error) { +func (t *telegram) OnFailure(s services.Service, f failures.Failure) (string, error) { msg := ReplaceVars(t.FailureData, s, f) out, err := t.sendMessage(msg) return out, err } // OnSuccess will trigger successful service -func (t *telegram) OnSuccess(s *services.Service) (string, error) { - msg := ReplaceVars(t.SuccessData, s, nil) +func (t *telegram) OnSuccess(s services.Service) (string, error) { + msg := ReplaceVars(t.SuccessData, s, failures.Failure{}) out, err := t.sendMessage(msg) return out, err } diff --git a/notifiers/telegram_test.go b/notifiers/telegram_test.go index 571dbd0b..b7895975 100644 --- a/notifiers/telegram_test.go +++ b/notifiers/telegram_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -31,6 +32,7 @@ func TestTelegramNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() if telegramToken == "" || telegramChannel == "" { t.Log("Telegram notifier testing skipped, missing TELEGRAM_TOKEN and TELEGRAM_CHANNEL environment variable") @@ -54,6 +56,11 @@ func TestTelegramNotifier(t *testing.T) { assert.True(t, Telegram.CanSend()) }) + t.Run("Telegram OnSave", func(t *testing.T) { + _, err := Telegram.OnSave() + assert.Nil(t, err) + }) + t.Run("Telegram OnFailure", func(t *testing.T) { _, err := Telegram.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/twilio.go b/notifiers/twilio.go index 2892fd1b..ec46d21a 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -90,14 +90,14 @@ func (t *twilio) sendMessage(message string) (string, error) { } // OnFailure will trigger failing service -func (t *twilio) OnFailure(s *services.Service, f *failures.Failure) (string, error) { +func (t *twilio) OnFailure(s services.Service, f failures.Failure) (string, error) { msg := ReplaceVars(t.FailureData, s, f) return t.sendMessage(msg) } // OnSuccess will trigger successful service -func (t *twilio) OnSuccess(s *services.Service) (string, error) { - msg := ReplaceVars(t.SuccessData, s, nil) +func (t *twilio) OnSuccess(s services.Service) (string, error) { + msg := ReplaceVars(t.SuccessData, s, failures.Failure{}) return t.sendMessage(msg) } diff --git a/notifiers/twilio_test.go b/notifiers/twilio_test.go index d12a8bfe..f7195811 100644 --- a/notifiers/twilio_test.go +++ b/notifiers/twilio_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -29,6 +30,7 @@ func TestTwilioNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() if TWILIO_SID == "" || TWILIO_SECRET == "" { t.Log("twilio notifier testing skipped, missing TWILIO_SID and TWILIO_SECRET environment variable") @@ -54,6 +56,11 @@ func TestTwilioNotifier(t *testing.T) { assert.True(t, Twilio.CanSend()) }) + t.Run("Twilio OnSave", func(t *testing.T) { + _, err := Twilio.OnSave() + assert.Nil(t, err) + }) + t.Run("Twilio OnFailure", func(t *testing.T) { _, err := Twilio.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/notifiers/webhook.go b/notifiers/webhook.go index b1ff300d..8f3d520c 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -127,7 +127,7 @@ func (w *webhooker) OnTest() (string, error) { } // OnFailure will trigger failing service -func (w *webhooker) OnFailure(s *services.Service, f *failures.Failure) (string, error) { +func (w *webhooker) OnFailure(s services.Service, f failures.Failure) (string, error) { msg := ReplaceVars(w.FailureData, s, f) resp, err := w.sendHttpWebhook(msg) if err != nil { @@ -139,8 +139,8 @@ func (w *webhooker) OnFailure(s *services.Service, f *failures.Failure) (string, } // OnSuccess will trigger successful service -func (w *webhooker) OnSuccess(s *services.Service) (string, error) { - msg := ReplaceVars(w.SuccessData, s, nil) +func (w *webhooker) OnSuccess(s services.Service) (string, error) { + msg := ReplaceVars(w.SuccessData, s, failures.Failure{}) resp, err := w.sendHttpWebhook(msg) if err != nil { return "", err diff --git a/notifiers/webhook_test.go b/notifiers/webhook_test.go index eb5fcbfa..bb70d8bc 100644 --- a/notifiers/webhook_test.go +++ b/notifiers/webhook_test.go @@ -2,6 +2,7 @@ package notifiers import ( "github.com/statping/statping/database" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" @@ -27,6 +28,7 @@ func TestWebhookNotifier(t *testing.T) { require.Nil(t, err) db.AutoMigrate(¬ifications.Notification{}) notifications.SetDB(db) + core.Example() t.Run("Load webhooker", func(t *testing.T) { Webhook.Host = webhookTestUrl @@ -46,6 +48,11 @@ func TestWebhookNotifier(t *testing.T) { assert.True(t, Webhook.CanSend()) }) + t.Run("webhooker OnSave", func(t *testing.T) { + _, err := Webhook.OnSave() + assert.Nil(t, err) + }) + t.Run("webhooker OnFailure", func(t *testing.T) { _, err := Webhook.OnFailure(services.Example(false), failures.Example()) assert.Nil(t, err) diff --git a/types/core/samples.go b/types/core/samples.go index f11cb798..700cc848 100644 --- a/types/core/samples.go +++ b/types/core/samples.go @@ -5,6 +5,22 @@ import ( "github.com/statping/statping/utils" ) +func Example() *Core { + core := &Core{ + Name: "Statping Testing", + Description: "This is a instance that runs for tests", + ApiSecret: "exampleapisecret", + Domain: "http://localhost:8080", + CreatedAt: utils.Now(), + UseCdn: null.NewNullBool(false), + Footer: null.NewNullString(""), + MigrationId: utils.Now().Unix(), + Language: "en", + } + App = core + return App +} + func Samples() error { apiSecret := utils.Params.GetString("API_SECRET") diff --git a/types/core/struct.go b/types/core/struct.go index 88d51a13..fdcc6c50 100644 --- a/types/core/struct.go +++ b/types/core/struct.go @@ -43,7 +43,6 @@ type Core struct { } type OAuth struct { - Domains string `gorm:"column:oauth_domains" json:"oauth_domains" scope:"admin"` 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"` @@ -55,6 +54,7 @@ type OAuth struct { 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" scope:"admin"` + SlackUsers string `gorm:"column:slack_users" json:"slack_users" scope:"admin"` } // AllNotifiers contains all the Notifiers loaded diff --git a/types/failures/samples.go b/types/failures/samples.go index 57fa4737..cd03f296 100644 --- a/types/failures/samples.go +++ b/types/failures/samples.go @@ -12,8 +12,8 @@ var ( log = utils.Log.WithField("type", "failure") ) -func Example() *Failure { - return &Failure{ +func Example() Failure { + return Failure{ Id: 48533, Issue: "Response did not response a 200 status code", Method: "", diff --git a/types/notifications/methods.go b/types/notifications/methods.go index 71c82337..b17b12ea 100644 --- a/types/notifications/methods.go +++ b/types/notifications/methods.go @@ -7,16 +7,15 @@ import ( "time" ) -func (n *Notification) Name() string { +func (n Notification) Name() string { newName := strings.ToLower(n.Method) newName = strings.ReplaceAll(newName, " ", "_") return newName } // LastSent returns a time.Duration of the last sent notification for the notifier -func (n *Notification) LastSent() time.Duration { - since := time.Since(n.lastSent) - return since +func (n Notification) LastSent() time.Duration { + return time.Since(n.lastSent) } func (n *Notification) CanSend() bool { diff --git a/types/notifier/interface.go b/types/notifier/interface.go index 405bb927..2f84d2d0 100644 --- a/types/notifier/interface.go +++ b/types/notifier/interface.go @@ -7,8 +7,8 @@ import ( // Notifier interface is required to create a new Notifier type Notifier interface { - OnSuccess(*services.Service) (string, error) // OnSuccess is triggered when a service is successful - OnFailure(*services.Service, *failures.Failure) (string, error) // OnFailure is triggered when a service is failing - OnTest() (string, error) // OnTest is triggered for testing - OnSave() (string, error) // OnSave is triggered for when saved + OnSuccess(services.Service) (string, error) // OnSuccess is triggered when a service is successful + OnFailure(services.Service, failures.Failure) (string, error) // OnFailure is triggered when a service is failing + OnTest() (string, error) // OnTest is triggered for testing + OnSave() (string, error) // OnSave is triggered for when saved } diff --git a/types/services/failures.go b/types/services/failures.go index d670f58a..36027249 100644 --- a/types/services/failures.go +++ b/types/services/failures.go @@ -20,15 +20,14 @@ func (s *Service) FailuresSince(t time.Time) failures.Failurer { return failures.Since(t, s) } -func (s *Service) DowntimeAgo() string { - last := s.LastOnline - if last.IsZero() { +func (s Service) DowntimeAgo() string { + if s.LastOnline.IsZero() { return "Never been online" } - return humanize.Time(last) + return humanize.Time(s.LastOnline) } -func (s *Service) DowntimeText() string { +func (s Service) DowntimeText() string { last := s.AllFailures().Last() if last == nil { return "" diff --git a/types/services/methods.go b/types/services/methods.go index 60132b72..54ee3bff 100644 --- a/types/services/methods.go +++ b/types/services/methods.go @@ -61,12 +61,12 @@ func (s *Service) LoadTLSCert() (*tls.Config, error) { return config, nil } -func (s *Service) Duration() time.Duration { +func (s Service) Duration() time.Duration { return time.Duration(s.Interval) * time.Second } // Start will create a channel for the service checking go routine -func (s *Service) UptimeData(hits []*hits.Hit, fails []*failures.Failure) (*UptimeSeries, error) { +func (s Service) UptimeData(hits []*hits.Hit, fails []*failures.Failure) (*UptimeSeries, error) { if len(hits) == 0 { return nil, errors.New("service does not have any successful hits") } @@ -291,12 +291,12 @@ func (s *Service) UpdateStats() *Service { } // AvgTime will return the average amount of time for a service to response back successfully -func (s *Service) AvgTime() int64 { +func (s Service) AvgTime() int64 { return s.AllHits().Avg() } // OnlineDaysPercent returns the service's uptime percent within last 24 hours -func (s *Service) OnlineDaysPercent(days int) float32 { +func (s Service) OnlineDaysPercent(days int) float32 { ago := utils.Now().Add(-time.Duration(days) * types.Day) return s.OnlineSince(ago) } @@ -326,12 +326,12 @@ func (s *Service) OnlineSince(ago time.Time) float32 { return s.Online24Hours } -func (s *Service) Uptime() utils.Duration { +func (s Service) Uptime() utils.Duration { return utils.Duration{Duration: utils.Now().Sub(s.LastOffline)} } // Downtime returns the amount of time of a offline service -func (s *Service) Downtime() utils.Duration { +func (s Service) Downtime() utils.Duration { return utils.Duration{Duration: utils.Now().Sub(s.LastOnline)} } diff --git a/types/services/notifier.go b/types/services/notifier.go index 5a822078..a51b9a62 100644 --- a/types/services/notifier.go +++ b/types/services/notifier.go @@ -26,9 +26,9 @@ func FindNotifier(method string) *notifications.Notification { } type ServiceNotifier interface { - OnSuccess(*Service) (string, error) // OnSuccess is triggered when a service is successful - OnFailure(*Service, *failures.Failure) (string, error) // OnFailure is triggered when a service is failing - OnTest() (string, error) // OnTest is triggered for testing - OnSave() (string, error) // OnSave is triggered for testing - Select() *notifications.Notification // OnTest is triggered for testing + OnSuccess(Service) (string, error) // OnSuccess is triggered when a service is successful + OnFailure(Service, failures.Failure) (string, error) // OnFailure is triggered when a service is failing + OnTest() (string, error) // OnTest is triggered for testing + OnSave() (string, error) // OnSave is triggered for testing + Select() *notifications.Notification // OnTest is triggered for testing } diff --git a/types/services/routine.go b/types/services/routine.go index 30eb2b8d..db70dea0 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -353,7 +353,7 @@ func sendSuccess(s *Service) { notif := n.Select() if notif.CanSend() { log.Infof("Sending notification to: %s!", notif.Method) - if _, err := n.OnSuccess(s); err != nil { + if _, err := n.OnSuccess(*s); err != nil { notif.Logger().Errorln(err) } s.UserNotified = true @@ -405,7 +405,7 @@ func sendFailure(s *Service, f *failures.Failure) { notif := n.Select() if notif.CanSend() { log.Infof("Sending Failure notification to: %s!", notif.Method) - if _, err := n.OnFailure(s, f); err != nil { + if _, err := n.OnFailure(*s, *f); err != nil { notif.Logger().WithField("failure", f.Issue).Errorln(err) } s.UserNotified = true diff --git a/types/services/samples.go b/types/services/samples.go index aaed0904..c979ac8c 100644 --- a/types/services/samples.go +++ b/types/services/samples.go @@ -6,8 +6,8 @@ import ( "time" ) -func Example(online bool) *Service { - return &Service{ +func Example(online bool) Service { + return Service{ Id: 6283, Name: "Statping Example", Domain: "https://statping.com",