Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate channel management with the IC database #2248

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
create table channels (
id serial primary key,
intelligence_channel_id integer not null,
scope exts.ltree not null,
name generic_string not null,
purchase_location integer not null,
created_at generic_timestamp not null,
updated_at generic_timestamp not null
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
create table channels_search_view (
id bigint primary key,
scope exts.ltree not null,
name generic_string not null,
hosts jsonb default '[]',
organization_name generic_string not null,
purchase_location generic_string not null,
created_at json_timestamp,
updated_at json_timestamp
);
13 changes: 13 additions & 0 deletions remote/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM golang:alpine

RUN apk add --no-cache ca-certificates

RUN mkdir -p /remote
ADD . /go/src/github.com/FoxComm/highlander/remote
WORKDIR /go/src/github.com/FoxComm/highlander/remote
RUN go build -o remote main.go && \
cp remote /remote && \
rm -rf /go
WORKDIR /remote

CMD /remote/remote 2>&1 | tee /logs/remote.log
44 changes: 29 additions & 15 deletions remote/controllers/channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,60 @@ package controllers
import (
"net/http"

"github.com/FoxComm/highlander/remote/models/phoenix"
"github.com/FoxComm/highlander/remote/payloads"
"github.com/FoxComm/highlander/remote/responses"
"github.com/FoxComm/highlander/remote/services"
"github.com/FoxComm/highlander/remote/utils/failures"
"github.com/jinzhu/gorm"
)

type Channels struct {
phxDB *gorm.DB
dbs *services.RemoteDBs
}

func NewChannels(phxDB *gorm.DB) *Channels {
return &Channels{phxDB: phxDB}
func NewChannels(dbs *services.RemoteDBs) *Channels {
return &Channels{dbs: dbs}
}

// GetChannel finds a single channel by its ID.
func (ctrl *Channels) GetChannel(id int) ControllerFunc {
return func() (*responses.Response, failures.Failure) {
channel := &phoenix.Channel{}

if err := services.FindChannelByID(ctrl.phxDB, id, channel); err != nil {
return nil, err
resp, fail := services.FindChannelByID(ctrl.dbs, id)
if fail != nil {
return nil, fail
}

resp := responses.NewChannel(channel)
return responses.NewResponse(http.StatusOK, resp), nil
}
}

// CreateChannel creates a new channel.
func (ctrl *Channels) CreateChannel(payload *payloads.CreateChannel) ControllerFunc {
return func() (*responses.Response, failures.Failure) {
phxChannel := payload.PhoenixModel()

if err := services.InsertChannel(ctrl.phxDB, phxChannel); err != nil {
return nil, err
resp, fail := services.InsertChannel(ctrl.dbs, payload)
if fail != nil {
return nil, fail
}

resp := responses.NewChannel(phxChannel)
return responses.NewResponse(http.StatusCreated, resp), nil
}
}

// UpdateChannel updates an existing channel.
func (ctrl *Channels) UpdateChannel(id int, payload *payloads.UpdateChannel) ControllerFunc {
return func() (*responses.Response, failures.Failure) {

// existingPhxChannel := &phoenix.Channel{}
// if err := services.FindChannelByID(ctrl.dbs, id, existingPhxChannel); err != nil {
// return nil, err
// }

// phxChannel := payload.PhoenixModel(existingPhxChannel)
// if err := services.UpdateChannel(ctrl.dbs, phxChannel); err != nil {
// return nil, err
// }

// resp := responses.NewChannel(phxChannel)
// return responses.NewResponse(http.StatusOK, resp), nil
return nil, nil
}
}
18 changes: 15 additions & 3 deletions remote/controllers/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ func Start() {
log.Fatal(err)
}

phxDB, err := services.NewPhoenixConnection(config)
dbs, err := services.NewRemoteDBs(config)
if err != nil {
log.Fatal(err)
}

defer phxDB.Close()
channelsCtrl := NewChannels(phxDB)
channelsCtrl := NewChannels(dbs)
pingCtrl := NewPing(dbs)

r := NewRouter()

r.GET("/v1/public/health", func(fc *FoxContext) error {
return fc.Run(pingCtrl.GetHealth())
})

r.GET("/v1/public/channels/:id", func(fc *FoxContext) error {
id := fc.ParamInt("id")
return fc.Run(channelsCtrl.GetChannel(id))
Expand All @@ -36,5 +40,13 @@ func Start() {
return fc.Run(channelsCtrl.CreateChannel(&payload))
})

r.PATCH("/v1/public/channels/:id", func(fc *FoxContext) error {
id := fc.ParamInt("id")
payload := payloads.UpdateChannel{}
fc.BindJSON(&payload)

return fc.Run(channelsCtrl.UpdateChannel(id, &payload))
})

r.Run(config.Port)
}
42 changes: 42 additions & 0 deletions remote/controllers/fox_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,37 @@ func NewFoxContext(c echo.Context) *FoxContext {
return &FoxContext{c, nil}
}

func (fc *FoxContext) getJWT() (*JWT, failures.Failure) {
jwtStr, fail := fc.getJWTString()
if fail != nil {
return nil, fail
}

jwt, err := NewJWT(jwtStr)
if err != nil {
return nil, failures.New(err)
}

return jwt, nil
}

func (fc *FoxContext) getJWTString() (string, failures.Failure) {
// Try to get from the header first.
req := fc.Request()
jwt, ok := req.Header["Jwt"]
if ok && len(jwt) > 0 {
return jwt[0], nil
}

// Try a cookie.
cookie, err := req.Cookie("JWT")
if err != nil {
return "", failures.New(err)
}

return cookie.Value, nil
}

// BindJSON grabs the JSON payload and unmarshals it into the interface provided.
func (fc *FoxContext) BindJSON(payload payloads.Payload) {
if fc.failure != nil {
Expand All @@ -39,6 +70,17 @@ func (fc *FoxContext) BindJSON(payload payloads.Payload) {
fc.failure = err
return
}

scoped, ok := payload.(payloads.Scoped)
if ok {
jwt, fail := fc.getJWT()
if fail != nil {
fc.failure = fail
return
}

scoped.EnsureScope(jwt.Scope())
}
}

// ParamInt parses an integer from the parameters list (as defined by the URI).
Expand Down
56 changes: 56 additions & 0 deletions remote/controllers/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package controllers

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"
)

// JWT is a simplified JWT wrapper for our usage. Since we rely on Isaac for
// validation, we just get customer and scope information.
type JWT struct {
Header map[string]interface{}
Payload map[string]interface{}
}

// NewJWT parses the JWT from a string.
func NewJWT(jwtStr string) (*JWT, error) {
parts := strings.Split(jwtStr, ".")
if len(parts) != 3 {
return nil, errors.New("JWT is malformed")
}

headerBytes, err := base64.URLEncoding.DecodeString(parts[0])
if err != nil {
return nil, fmt.Errorf("Error decoding header with error: %s", err)
}

header := map[string]interface{}{}
if err := json.Unmarshal(headerBytes, &header); err != nil {
return nil, fmt.Errorf("Error marshalling header with error: %s", err)
}

payloadBytes, err := base64.URLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("Error decoding payload with error: %s", err)
}

payload := map[string]interface{}{}
if err := json.Unmarshal(payloadBytes, &payload); err != nil {
return nil, fmt.Errorf("Error marshalling payload with error: %s", err)
}

return &JWT{Header: header, Payload: payload}, nil
}

// Scope gets the scope string passed in the JWT.
func (j JWT) Scope() string {
scope, ok := j.Payload["Scope"]
if !ok {
return ""
}

return scope.(string)
}
50 changes: 50 additions & 0 deletions remote/controllers/ping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package controllers

import (
"net/http"

"github.com/FoxComm/highlander/remote/responses"
"github.com/FoxComm/highlander/remote/services"
"github.com/FoxComm/highlander/remote/utils/failures"
)

// Ping is a really simple controller used for health checks.
type Ping struct {
dbs *services.RemoteDBs
}

// NewPing creates a new ping controller.
func NewPing(dbs *services.RemoteDBs) *Ping {
return &Ping{dbs: dbs}
}

type health struct {
Intelligence string `json:"intelligence"`
Phoenix string `json:"phoenix"`
}

// GetHealth tests the connection to the databases and returns the status.
func (ctrl *Ping) GetHealth() ControllerFunc {
return func() (*responses.Response, failures.Failure) {
icPingErr := ctrl.dbs.IC().Ping()
phxPingErr := ctrl.dbs.Phx().Ping()

statusCode := http.StatusOK
h := health{
Intelligence: "passed",
Phoenix: "passed",
}

if icPingErr != nil {
statusCode = http.StatusInternalServerError
h.Intelligence = "failed"
}

if phxPingErr != nil {
statusCode = http.StatusInternalServerError
h.Phoenix = "failed"
}

return responses.NewResponse(statusCode, h), nil
}
}
7 changes: 7 additions & 0 deletions remote/controllers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@ func (r *Router) POST(uri string, rf RouterFunc) {
return rf(fc)
})
}

func (r *Router) PATCH(uri string, rf RouterFunc) {
r.e.PATCH(uri, func(c echo.Context) error {
fc := c.(*FoxContext)
return rf(fc)
})
}
11 changes: 8 additions & 3 deletions remote/glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions remote/models/ic/channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ic

// Channel is the representation of what a channel looks like in River Rock.
type Channel struct {
ID int
OrganizationID int
}

func (c Channel) HostMaps(hosts []string, scope string) []*HostMap {
hostMaps := make([]*HostMap, len(hosts))

for idx, host := range hosts {
hostMap := &HostMap{
Host: host,
ChannelID: c.ID,
Scope: scope,
}

hostMaps[idx] = hostMap
}

return hostMaps
}
10 changes: 10 additions & 0 deletions remote/models/ic/host_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ic

// HostMap stores the relationship been site hostnames and channels.
// Used by River Rock to determine which channel to use for a given request.
type HostMap struct {
ID int
Host string
ChannelID int
Scope string
}
Loading