Skip to content

Commit

Permalink
Add routes for Slack authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
ngs committed Mar 24, 2018
1 parent af7179b commit 56618f9
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 114 deletions.
60 changes: 43 additions & 17 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,43 @@ import (

// App main appplication
type App struct {
Port int
ClientSecret string
ClientID string
SlackVerificationToken string
StateStoreKey string
TokenStoreKey string
TeamSpiritHost string
RedisConn redis.Conn
TimeoutDuration time.Duration
Port int
SalesforceClientSecret string
SalesforceClientID string
SlackClientSecret string
SlackClientID string
SlackVerificationToken string
StateStoreKey string
SalesforceTokenStoreKey string
SlackTokenStoreKey string
NotifyChannelStoreKey string
TeamSpiritHost string
RedisConn redis.Conn
TimeoutDuration time.Duration
}

// New Returns new app
func new() (*App, error) {
app := &App{}
clientSecret := os.Getenv("SALESFORCE_CLIENT_SECRET")
clientID := os.Getenv("SALESFORCE_CLIENT_ID")
salesforceClientSecret := os.Getenv("SALESFORCE_CLIENT_SECRET")
salesforceClientID := os.Getenv("SALESFORCE_CLIENT_ID")
slackClientSecret := os.Getenv("SLACK_CLIENT_SECRET")
slackClientID := os.Getenv("SLACK_CLIENT_ID")
slackVerificationToken := os.Getenv("SLACK_VERIFICATION_TOKEN")
teamSpilitHost := os.Getenv("TEAMSPIRIT_HOST")
var errVars = []string{}
if clientSecret == "" {
if salesforceClientSecret == "" {
errVars = append(errVars, "SALESFORCE_CLIENT_SECRET")
}
if clientID == "" {
if salesforceClientID == "" {
errVars = append(errVars, "SALESFORCE_CLIENT_ID")
}
if slackClientSecret == "" {
errVars = append(errVars, "SLACK_CLIENT_SECRET")
}
if slackClientID == "" {
errVars = append(errVars, "SLACK_CLIENT_ID")
}
if slackVerificationToken == "" {
errVars = append(errVars, "SLACK_VERIFICATION_TOKEN")
}
Expand All @@ -57,9 +69,21 @@ func new() (*App, error) {
}

if k := os.Getenv("OAUTH_TOKEN_STORE_KEY"); k != "" {
app.TokenStoreKey = k
app.SalesforceTokenStoreKey = k
} else {
app.SalesforceTokenStoreKey = "tsdakoku:oauth_tokens"
}

if k := os.Getenv("SLACK_TOKEN_STORE_KEY"); k != "" {
app.SlackTokenStoreKey = k
} else {
app.SlackTokenStoreKey = "tsdakoku:slack_tokens"
}

if k := os.Getenv("SLACK_NOTIFY_CHANNEL_STORE_KEY"); k != "" {
app.NotifyChannelStoreKey = k
} else {
app.TokenStoreKey = "tsdakoku:oauth_tokens"
app.NotifyChannelStoreKey = "tsdakoku:notify_channels"
}

duration, _ := strconv.Atoi(os.Getenv("SALESFORCE_TIMEOUT_MINUTES"))
Expand All @@ -69,8 +93,10 @@ func new() (*App, error) {
app.TimeoutDuration = time.Hour
}

app.ClientID = clientID
app.ClientSecret = clientSecret
app.SalesforceClientID = salesforceClientID
app.SalesforceClientSecret = salesforceClientSecret
app.SlackClientID = slackClientID
app.SlackClientSecret = slackClientSecret
app.SlackVerificationToken = slackVerificationToken
app.TeamSpiritHost = teamSpilitHost
if err := app.setupRedis(); err != nil {
Expand Down
14 changes: 10 additions & 4 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func (app *App) CleanRedis() {
app.RedisConn.Do("DEL", app.TokenStoreKey)
app.RedisConn.Do("DEL", app.SalesforceTokenStoreKey)
app.RedisConn.Do("DEL", app.StateStoreKey)
}

Expand All @@ -31,6 +31,8 @@ func TestNewApp(t *testing.T) {
for _, name := range []string{
"SALESFORCE_CLIENT_SECRET",
"SALESFORCE_CLIENT_ID",
"SLACK_CLIENT_SECRET",
"SLACK_CLIENT_ID",
"SLACK_VERIFICATION_TOKEN",
"TEAMSPIRIT_HOST",
"OAUTH_TOKEN_STORE_KEY",
Expand All @@ -41,13 +43,15 @@ func TestNewApp(t *testing.T) {
app, err := new()
for _, test := range []Test{
{false, app == nil},
{"SALESFORCE_CLIENT_SECRET, SALESFORCE_CLIENT_ID, SLACK_VERIFICATION_TOKEN, TEAMSPIRIT_HOST are not configured", err.Error()},
{"SALESFORCE_CLIENT_SECRET, SALESFORCE_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_CLIENT_ID, SLACK_VERIFICATION_TOKEN, TEAMSPIRIT_HOST are not configured", err.Error()},
} {
test.Compare(t)
}
for _, name := range []string{
"SALESFORCE_CLIENT_SECRET",
"SALESFORCE_CLIENT_ID",
"SLACK_CLIENT_SECRET",
"SLACK_CLIENT_ID",
"SLACK_VERIFICATION_TOKEN",
"TEAMSPIRIT_HOST",
} {
Expand All @@ -58,20 +62,22 @@ func TestNewApp(t *testing.T) {
{false, app == nil},
{true, err == nil},
{"tsdakoku:states", app.StateStoreKey},
{"tsdakoku:oauth_tokens", app.TokenStoreKey},
{"tsdakoku:oauth_tokens", app.SalesforceTokenStoreKey},
{time.Hour, app.TimeoutDuration},
} {
test.Compare(t)
}
os.Setenv("STATE_STORE_KEY", "tsdakoku-test:states")
os.Setenv("OAUTH_TOKEN_STORE_KEY", "tsdakoku-test:oauth_tokens")
os.Setenv("SLACK_TOKEN_STORE_KEY", "tsdakoku-test:slack_tokens")
os.Setenv("SALESFORCE_TIMEOUT_MINUTES", "20")
app, err = new()
for _, test := range []Test{
{false, app == nil},
{true, err == nil},
{"tsdakoku-test:states", app.StateStoreKey},
{"tsdakoku-test:oauth_tokens", app.TokenStoreKey},
{"tsdakoku-test:oauth_tokens", app.SalesforceTokenStoreKey},
{"tsdakoku-test:slack_tokens", app.SlackTokenStoreKey},
{20 * time.Minute, app.TimeoutDuration},
} {
test.Compare(t)
Expand Down
57 changes: 35 additions & 22 deletions app/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,40 @@ import (

// Context in request
type Context struct {
RedisConn redis.Conn
Request *http.Request
ClientSecret string
ClientID string
UserID string
StateStoreKey string
TokenStoreKey string
TeamSpiritHost string
SlackVerificationToken string
TimeoutDuration time.Duration
TimeTableClient *timeTableClient
randomString func(len int) string
RedisConn redis.Conn
Request *http.Request
SalesforceClientSecret string
SalesforceClientID string
SlackClientSecret string
SlackClientID string
UserID string
StateStoreKey string
SalesforceTokenStoreKey string
SlackTokenStoreKey string
NotifyChannelStoreKey string
TeamSpiritHost string
SlackVerificationToken string
TimeoutDuration time.Duration
TimeTableClient *timeTableClient
randomString func(len int) string
}

func (app *App) createContext(r *http.Request) *Context {
return &Context{
RedisConn: app.RedisConn,
ClientID: app.ClientID,
ClientSecret: app.ClientSecret,
StateStoreKey: app.StateStoreKey,
TokenStoreKey: app.TokenStoreKey,
TeamSpiritHost: app.TeamSpiritHost,
SlackVerificationToken: app.SlackVerificationToken,
TimeoutDuration: app.TimeoutDuration,
Request: r,
randomString: randomString,
RedisConn: app.RedisConn,
SalesforceClientID: app.SalesforceClientID,
SalesforceClientSecret: app.SalesforceClientSecret,
SlackClientID: app.SlackClientID,
SlackClientSecret: app.SlackVerificationToken,
StateStoreKey: app.StateStoreKey,
SalesforceTokenStoreKey: app.SalesforceTokenStoreKey,
SlackTokenStoreKey: app.SlackTokenStoreKey,
NotifyChannelStoreKey: app.NotifyChannelStoreKey,
TeamSpiritHost: app.TeamSpiritHost,
SlackVerificationToken: app.SlackVerificationToken,
TimeoutDuration: app.TimeoutDuration,
Request: r,
randomString: randomString,
}
}

Expand All @@ -48,3 +56,8 @@ func (ctx *Context) getVariableInHash(hashKey string, key string) string {
}
return ""
}

func (ctx *Context) setVariableInHash(hashKey string, value interface{}) error {
_, err := redis.Bool(ctx.RedisConn.Do("HSET", hashKey, ctx.UserID, value))
return err
}
6 changes: 3 additions & 3 deletions app/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ func TestCreateContext(t *testing.T) {

for _, test := range []Test{
{false, ctx.RedisConn == nil},
{"SALESFORCE_CLIENT_ID is set!", ctx.ClientID},
{"SALESFORCE_CLIENT_SECRET is set!", ctx.ClientSecret},
{"SALESFORCE_CLIENT_ID is set!", ctx.SalesforceClientID},
{"SALESFORCE_CLIENT_SECRET is set!", ctx.SalesforceClientSecret},
{"tsdakoku-test:states", ctx.StateStoreKey},
{"tsdakoku-test:oauth_tokens", ctx.TokenStoreKey},
{"tsdakoku-test:oauth_tokens", ctx.SalesforceTokenStoreKey},
{"teamspirit-1234.cloudforce.test", ctx.TeamSpiritHost},
{"SLACK_VERIFICATION_TOKEN is set!", ctx.SlackVerificationToken},
{req, ctx.Request},
Expand Down
60 changes: 40 additions & 20 deletions app/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,26 @@ import (
"net/http"
"time"

"github.com/garyburd/redigo/redis"
"golang.org/x/oauth2"
)

func (ctx *Context) getOAuthCallbackURL() string {
return "https://" + ctx.Request.Host + "/oauth/callback"
func (ctx *Context) getSalesforceOAuthCallbackURL() string {
return "https://" + ctx.Request.Host + "/oauth/salesforce/callback"
}

func (ctx *Context) getAuthenticateURL(state string) string {
return "https://" + ctx.Request.Host + "/oauth/authenticate/" + state
func (ctx *Context) getSalesforceAuthenticateURL(state string) string {
return "https://" + ctx.Request.Host + "/oauth/salesforce/authenticate/" + state
}

func (ctx *Context) setAccessToken(token *oauth2.Token) error {
func (ctx *Context) getSlackOAuthCallbackURL() string {
return "https://" + ctx.Request.Host + "/oauth/slack/callback"
}

func (ctx *Context) getSlackAuthenticateURL(team, state string) string {
return "https://" + ctx.Request.Host + "/oauth/slack/authenticate/" + team + "/" + state
}

func (ctx *Context) setSalesforceAccessToken(token *oauth2.Token) error {
if ctx.UserID == "" {
return errors.New("UserID is not set")
}
Expand All @@ -31,16 +38,22 @@ func (ctx *Context) setAccessToken(token *oauth2.Token) error {
if err != nil {
return err
}
_, err = redis.Bool(ctx.RedisConn.Do("HSET", ctx.TokenStoreKey, ctx.UserID, tokenJSON))
return err
return ctx.setVariableInHash(ctx.SalesforceTokenStoreKey, tokenJSON)
}

func (ctx *Context) getOAuth2Config() *oauth2.Config {
func (ctx *Context) setSlackAccessToken(token string) error {
if ctx.UserID == "" {
return errors.New("UserID is not set")
}
return ctx.setVariableInHash(ctx.SlackTokenStoreKey, token)
}

func (ctx *Context) getSalesforceOAuth2Config() *oauth2.Config {
return &oauth2.Config{
ClientID: ctx.ClientID,
ClientSecret: ctx.ClientSecret,
ClientID: ctx.SalesforceClientID,
ClientSecret: ctx.SalesforceClientSecret,
Scopes: []string{},
RedirectURL: ctx.getOAuthCallbackURL(),
RedirectURL: ctx.getSalesforceOAuthCallbackURL(),
Endpoint: oauth2.Endpoint{
// https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_understanding_oauth_endpoints.htm
AuthURL: "https://login.salesforce.com/services/oauth2/authorize",
Expand All @@ -49,36 +62,43 @@ func (ctx *Context) getOAuth2Config() *oauth2.Config {
}
}

func (ctx *Context) getAccessTokenForUser() *oauth2.Token {
func (ctx *Context) getSalesforceAccessTokenForUser() *oauth2.Token {
if ctx.UserID == "" {
return nil
}
tokenJSON := ctx.getVariableInHash(ctx.TokenStoreKey, ctx.UserID)
tokenJSON := ctx.getVariableInHash(ctx.SalesforceTokenStoreKey, ctx.UserID)
var token oauth2.Token
if err := json.Unmarshal([]byte(tokenJSON), &token); err != nil {
return nil
}
return &token
}

func (ctx *Context) getAccessToken(code string, state string) (*oauth2.Token, error) {
config := ctx.getOAuth2Config()
func (ctx *Context) getSlackAccessTokenForUser() string {
if ctx.UserID == "" {
return ""
}
return ctx.getVariableInHash(ctx.SlackTokenStoreKey, ctx.UserID)
}

func (ctx *Context) getSalesforceAccessToken(code string, state string) (*oauth2.Token, error) {
config := ctx.getSalesforceOAuth2Config()
t, err := config.Exchange(context.TODO(), code)
if err != nil {
return nil, err
}
return t, nil
}

func (ctx *Context) getOAuth2Client() *http.Client {
token := ctx.getAccessTokenForUser()
func (ctx *Context) getSalesforceOAuth2Client() *http.Client {
token := ctx.getSalesforceAccessTokenForUser()
if token == nil {
return nil
}
src := ctx.getOAuth2Config().TokenSource(context.TODO(), token)
src := ctx.getSalesforceOAuth2Config().TokenSource(context.TODO(), token)
ts := oauth2.ReuseTokenSource(token, src)
if token, _ := ts.Token(); token != nil {
ctx.setAccessToken(token)
ctx.setSalesforceAccessToken(token)
}
return oauth2.NewClient(oauth2.NoContext, ts)
}

0 comments on commit 56618f9

Please sign in to comment.