From 032723eef1d8d2d9efa4616f5f7ffc7e2f059f87 Mon Sep 17 00:00:00 2001 From: Aiden <86704247+vabold@users.noreply.github.com> Date: Tue, 1 Aug 2023 09:32:11 -0400 Subject: [PATCH 1/2] Load configurable webhook URL --- srepanel/config.go | 3 +++ srepanel/main.go | 7 ++++--- srepanel/web/server.go | 7 ++++--- test_config.json | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/srepanel/config.go b/srepanel/config.go index 15bb6a4..3249173 100644 --- a/srepanel/config.go +++ b/srepanel/config.go @@ -11,6 +11,9 @@ type config struct { Discord struct { ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` + // TODO allow for more general webhooks + // For now, we assume that the webhook is for Discord + WebhookURL string `json:"webhook_url"` } `json:"discord"` Ghidra struct { Endpoint common.GhidraEndpoint `json:"endpoint"` diff --git a/srepanel/main.go b/srepanel/main.go index 439ffd4..8514d96 100644 --- a/srepanel/main.go +++ b/srepanel/main.go @@ -108,9 +108,10 @@ func main() { issuer := token.NewIssuer((*[32]byte)(secrets.HMACSecret)) webConfig := web.Config{ - GhidraEndpoint: &cfg.Ghidra.Endpoint, - Links: cfg.Links, - Dev: *dev, + GhidraEndpoint: &cfg.Ghidra.Endpoint, + Links: cfg.Links, + DiscordWebhookURL: cfg.Discord.WebhookURL, + Dev: *dev, } server, err := web.NewServer(&webConfig, db, auth, &issuer, &acls) if err != nil { diff --git a/srepanel/web/server.go b/srepanel/web/server.go index 8fc8608..61c267e 100644 --- a/srepanel/web/server.go +++ b/srepanel/web/server.go @@ -35,9 +35,10 @@ func init() { } type Config struct { - GhidraEndpoint *common.GhidraEndpoint - Links []common.Link - Dev bool // developer mode + GhidraEndpoint *common.GhidraEndpoint + Links []common.Link + DiscordWebhookURL string + Dev bool // developer mode } type Server struct { diff --git a/test_config.json b/test_config.json index c1dbf13..1593f53 100644 --- a/test_config.json +++ b/test_config.json @@ -1,7 +1,8 @@ { "discord": { "client_id": "xxx", - "client_secret": "xxx" + "client_secret": "xxx", + "webhook_url": "xxx" }, "base_url": "http://localhost:8080", "ghidra": { From 22ccd92f37956ba65e1e04879f6ae858f4685cbe Mon Sep 17 00:00:00 2001 From: Aiden <86704247+vabold@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:43:26 -0400 Subject: [PATCH 2/2] Send access request to webhook URL --- srepanel/common/types.go | 5 +- srepanel/discord/api.go | 37 +++++++++ srepanel/{discord_auth => discord}/auth.go | 8 +- srepanel/main.go | 4 +- srepanel/token/token.go | 19 +++-- srepanel/web/request_access.go | 87 ++++++++++++++++++++++ srepanel/web/server.go | 7 +- srepanel/web/templates/home.gohtml | 8 +- 8 files changed, 156 insertions(+), 19 deletions(-) create mode 100644 srepanel/discord/api.go rename srepanel/{discord_auth => discord}/auth.go (94%) create mode 100644 srepanel/web/request_access.go diff --git a/srepanel/common/types.go b/srepanel/common/types.go index cdaee8a..93b2e5b 100644 --- a/srepanel/common/types.go +++ b/srepanel/common/types.go @@ -1,8 +1,9 @@ package common type Identity struct { - ID uint64 `json:"id"` - Username string `json:"username"` + ID uint64 `json:"id"` + Username string `json:"username"` + AvatarHash string `json:"avatar"` } type GhidraEndpoint struct { diff --git a/srepanel/discord/api.go b/srepanel/discord/api.go new file mode 100644 index 0000000..67d2214 --- /dev/null +++ b/srepanel/discord/api.go @@ -0,0 +1,37 @@ +package discord + +// ----------- // +// Channel API // +// ----------- // + +// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure +type EmbedAuthor struct { + Name string `json:"name"` + IconURL string `json:"icon_url"` +} + +// https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure +type EmbedField struct { + Name string `json:"name"` + Value string `json:"value"` + Inline bool `json:"inline"` +} + +// https://discord.com/developers/docs/resources/channel#embed-object +type Embed struct { + Title string `json:"title"` + Color int `json:"color"` + Author EmbedAuthor `json:"author"` + Fields []EmbedField `json:"fields"` +} + +// ----------- // +// Webhook API // +// ----------- // + +// https://discord.com/developers/docs/resources/webhook#execute-webhook +type WebhookMessage struct { + Username string `json:"username"` + AvatarURL string `json:"avatar_url"` + Embeds []Embed `json:"embeds"` +} diff --git a/srepanel/discord_auth/auth.go b/srepanel/discord/auth.go similarity index 94% rename from srepanel/discord_auth/auth.go rename to srepanel/discord/auth.go index e6082a7..a95e8cf 100644 --- a/srepanel/discord_auth/auth.go +++ b/srepanel/discord/auth.go @@ -1,4 +1,4 @@ -package discord_auth +package discord import ( "context" @@ -98,6 +98,7 @@ func (c *Auth) GetDiscordIdentity(ctx context.Context, token *oauth2.Token) (ide User struct { ID uint64 `json:"id,string"` Username string `json:"username"` + Avatar string `json:"avatar"` } `json:"user"` } if err := json.NewDecoder(res.Body).Decode(&info); err != nil { @@ -109,7 +110,8 @@ func (c *Auth) GetDiscordIdentity(ctx context.Context, token *oauth2.Token) (ide } return &common.Identity{ - ID: info.User.ID, - Username: info.User.Username, + ID: info.User.ID, + Username: info.User.Username, + AvatarHash: info.User.Avatar, }, nil } diff --git a/srepanel/main.go b/srepanel/main.go index 8514d96..6670a2b 100644 --- a/srepanel/main.go +++ b/srepanel/main.go @@ -13,7 +13,7 @@ import ( "os/signal" "go.mkw.re/ghidra-panel/database" - "go.mkw.re/ghidra-panel/discord_auth" + "go.mkw.re/ghidra-panel/discord" "go.mkw.re/ghidra-panel/token" "go.mkw.re/ghidra-panel/web" ) @@ -103,7 +103,7 @@ func main() { redirectURL := cfg.BaseURL + "/redirect" - auth := discord_auth.NewAuth(cfg.Discord.ClientID, cfg.Discord.ClientSecret, redirectURL) + auth := discord.NewAuth(cfg.Discord.ClientID, cfg.Discord.ClientSecret, redirectURL) issuer := token.NewIssuer((*[32]byte)(secrets.HMACSecret)) diff --git a/srepanel/token/token.go b/srepanel/token/token.go index 1994df8..0f0c4a8 100644 --- a/srepanel/token/token.go +++ b/srepanel/token/token.go @@ -28,9 +28,10 @@ func NewIssuer(secret *[32]byte) Issuer { } type Claims struct { - Sub uint64 `json:"sub,string"` - Name string `json:"name"` - Iat int64 `json:"iat"` + Sub uint64 `json:"sub,string"` + Name string `json:"name"` + AvatarHash string `json:"avatar"` + Iat int64 `json:"iat"` } func (c *Claims) String() string { @@ -43,9 +44,10 @@ func (c *Claims) String() string { func (iss Issuer) Issue(ident *common.Identity) string { claims := &Claims{ - Sub: ident.ID, - Name: ident.Username, - Iat: time.Now().Unix(), + Sub: ident.ID, + Name: ident.Username, + AvatarHash: ident.AvatarHash, + Iat: time.Now().Unix(), } body := jwtPrefix + claims.String() return body + "." + iss.sign(body) @@ -108,7 +110,8 @@ func (iss Issuer) Verify(jwt string) (ident *common.Identity, ok bool) { // Reconstruct identity return &common.Identity{ - ID: claims.Sub, - Username: claims.Name, + ID: claims.Sub, + Username: claims.Name, + AvatarHash: claims.AvatarHash, }, true } diff --git a/srepanel/web/request_access.go b/srepanel/web/request_access.go new file mode 100644 index 0000000..0d4fa46 --- /dev/null +++ b/srepanel/web/request_access.go @@ -0,0 +1,87 @@ +package web + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + + "go.mkw.re/ghidra-panel/common" + "go.mkw.re/ghidra-panel/discord" +) + +func (s *Server) handleRequestAccess(wr http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + http.Error(wr, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + ident, ok := s.checkAuth(req) + if !ok { + http.Error(wr, "Not authorized", http.StatusUnauthorized) + return + } + + if err := req.ParseForm(); err != nil { + http.Error(wr, "Bad request", http.StatusBadRequest) + return + } + + // TODO query ACL to ensure user account exists + + message := s.writeMessage(ident) + + // Send access request message + ctx := context.TODO() + payloadBuf, _ := json.Marshal(&message) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.Config.DiscordWebhookURL, bytes.NewReader(payloadBuf)) + if err != nil { + http.Error(wr, "Internal server error", http.StatusInternalServerError) + return + } + + req.Header.Set("content-type", "application/json") + res, err := http.DefaultClient.Do(req) + if err != nil { + // TODO properly handle + http.Redirect(wr, req, "/?access_request=failure", http.StatusTemporaryRedirect) + return + } + defer res.Body.Close() + + http.Redirect(wr, req, "/?access_request=success", http.StatusTemporaryRedirect) +} + +func (s *Server) writeMessage(ident *common.Identity) discord.WebhookMessage { + embedAuthor := discord.EmbedAuthor{ + Name: ident.Username, + IconURL: fmt.Sprintf("https://cdn.discordapp.com/avatars/%d/%s.png", ident.ID, ident.AvatarHash), + } + + hostnameField := discord.EmbedField{ + Name: "Hostname", + Value: s.Config.GhidraEndpoint.Hostname, + Inline: true, + } + + portField := discord.EmbedField{ + Name: "Port", + Value: strconv.FormatUint(uint64(s.Config.GhidraEndpoint.Port), 10), + Inline: true, + } + + ghidraEmbed := discord.Embed{ + Title: fmt.Sprintf("%s has requested access to the following Ghidra server:", ident.Username), + Color: 0x77DD77, + Author: embedAuthor, + Fields: []discord.EmbedField{hostnameField, portField}, + } + + return discord.WebhookMessage{ + Username: "Panel", + AvatarURL: "", + Embeds: []discord.Embed{ghidraEmbed}, + } +} diff --git a/srepanel/web/server.go b/srepanel/web/server.go index 61c267e..3069614 100644 --- a/srepanel/web/server.go +++ b/srepanel/web/server.go @@ -7,7 +7,7 @@ import ( "go.mkw.re/ghidra-panel/common" "go.mkw.re/ghidra-panel/database" - "go.mkw.re/ghidra-panel/discord_auth" + "go.mkw.re/ghidra-panel/discord" "go.mkw.re/ghidra-panel/ghidra" "go.mkw.re/ghidra-panel/token" ) @@ -44,7 +44,7 @@ type Config struct { type Server struct { Config *Config DB *database.DB - Auth *discord_auth.Auth + Auth *discord.Auth Issuer *token.Issuer ACLs *ghidra.ACLMon } @@ -52,7 +52,7 @@ type Server struct { func NewServer( config *Config, db *database.DB, - auth *discord_auth.Auth, + auth *discord.Auth, issuer *token.Issuer, acls *ghidra.ACLMon, ) (*Server, error) { @@ -73,6 +73,7 @@ func (s *Server) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("/logout", s.handleLogout) mux.HandleFunc("/update_password", s.handleUpdatePassword) + mux.HandleFunc("/request_access", s.handleRequestAccess) // Create file server for assets mux.Handle("/assets/", http.FileServer(http.FS(assets))) diff --git a/srepanel/web/templates/home.gohtml b/srepanel/web/templates/home.gohtml index 21835c2..a72f957 100644 --- a/srepanel/web/templates/home.gohtml +++ b/srepanel/web/templates/home.gohtml @@ -40,8 +40,14 @@ {{ else }}
Your account does not have any access to Ghidra repositories.
- + {{ if .UserState.HasPassword }} + + {{ else }} +You cannot request access to Ghidra repositories without setting a password. Please set one to continue!
{{ end }} + {{ end }}