Skip to content

Commit

Permalink
[WIP] jwt initial support
Browse files Browse the repository at this point in the history
  • Loading branch information
jparound30 committed Feb 27, 2021
1 parent 70ae8f6 commit e3d193c
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/apiconnstate.json
/cert/server.key
/cert/server.crt

config.json

# Created by .ignore support plugin (hsz.mobi)
### macOS template
Expand Down
149 changes: 149 additions & 0 deletions apiconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@ package goboxer

import (
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"sync"
"time"

"github.com/dgrijalva/jwt-go/v4"
"github.com/google/uuid"
"github.com/youmark/pkcs8"
"golang.org/x/xerrors"
)

const (
refreshMarginInSec = 60.0
)

var uuidGen uuid.UUID

func init() {
var err error
uuidGen, err = uuid.NewRandom()
if err != nil {
log.Panicf("cannot initialize ID generator")
}
}

// TODO Suppressing Notifications https://developer.box.com/reference#suppressing-notifications

// APIConnRefreshNotifier is the interface that notifies the refresh result AccessToken/RefreshToken
Expand Down Expand Up @@ -283,3 +299,136 @@ func (ac *APIConn) lockAccessToken() (string, error) {
func (ac *APIConn) unlockAccessToken() {
ac.accessTokenLock.Unlock()
}

type JwtConfig struct {
BoxAppSettings struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
AppAuth struct {
PublicKeyID string `json:"publicKeyID"`
PrivateKey string `json:"privateKey"`
Passphrase string `json:"passphrase"`
} `json:"appAuth"`
} `json:"boxAppSettings"`
EnterpriseID string `json:"enterpriseID"`
}

type BoxJwt struct {
BoxSubType string `json:"box_sub_type"`
Audience string `json:"aud"`
ExpiresAt int64 `json:"exp"`
jwt.StandardClaims
}

// NewAPIConnWithJwtConfig allocates and returns a new Box API connection from Jwt config.
func NewAPIConnWithJwtConfig(jwtConfigPath string) (*APIConn, error) {
// 1. Read JSON configuration
configFile, err := ioutil.ReadFile(jwtConfigPath)
if err != nil {
return nil, xerrors.Errorf("failed to read Jwt Config File. %w", err)
}
jwtConfig := JwtConfig{}
err = json.Unmarshal(configFile, &jwtConfig)
if err != nil {
return nil, xerrors.Errorf("failed to parse Jwt Config File. %w", err)
}

// 2. Decrypt private key
block, _ := pem.Decode([]byte(jwtConfig.BoxAppSettings.AppAuth.PrivateKey))
if block == nil {
return nil, xerrors.New("failed to decode a PEM")
}

pkey, _, err := pkcs8.ParsePrivateKey(
block.Bytes,
[]byte(jwtConfig.BoxAppSettings.AppAuth.Passphrase),
)
if err != nil {
log.Printf("failed to parse private key. %+v", err)
return nil, xerrors.Errorf("failed to parse private key. %w", err)
}

// 3. Create JWT assertion
boxJwt := BoxJwt{
BoxSubType: "enterprise",
Audience: "https://api.box.com/oauth2/token",
ExpiresAt: time.Now().Add(time.Duration(55) * time.Second).Unix(),
StandardClaims: jwt.StandardClaims{
Issuer: jwtConfig.BoxAppSettings.ClientID,
Subject: jwtConfig.EnterpriseID,
ID: uuidGen.URN(),
IssuedAt: nil,
NotBefore: nil,
},
}

token := jwt.NewWithClaims(jwt.SigningMethodRS256, boxJwt)
token.Header["kid"] = jwtConfig.BoxAppSettings.AppAuth.PublicKeyID
signedString, err := token.SignedString(pkey)
if err != nil {
log.Printf("failed to signing token : %s", err)
return nil, xerrors.New("failed to signing token")
}
log.Printf("TOKEN: %s", signedString)

instance := &APIConn{
ClientID: jwtConfig.BoxAppSettings.ClientID,
ClientSecret: jwtConfig.BoxAppSettings.ClientSecret,
}
instance.commonInit()

err = instance.authenticateWithJwt(signedString)
if err != nil {
return nil, xerrors.Errorf("failed to authenticate with jwt. %w", err)
}
return instance, nil
}

func (ac *APIConn) authenticateWithJwt(jwt string) error {
ac.rwLock.Lock()
defer ac.rwLock.Unlock()

var params = url.Values{}
params.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
params.Add("assertion", jwt)
params.Add("client_id", ac.ClientID)
params.Add("client_secret", ac.ClientSecret)

header := http.Header{}
header.Add(httpHeaderContentType, ContentTypeFormUrlEncoded)

request := NewRequest(ac, ac.TokenURL, POST, header, strings.NewReader(params.Encode()))
request.shouldAuthenticate = false

resp, err := request.Send()
if err != nil {
ac.notifyFail(err)
return err
}

if resp.ResponseCode != http.StatusOK {
log.Printf("error: %+v", resp)
log.Printf("body: %s", string(resp.Body))
err := xerrors.New("failed to Authenticate with Jwt")
ac.notifyFail(err)
return err
}

var tokenResp tokenResponse
if err := json.Unmarshal(resp.Body, &tokenResp); err != nil {
log.Printf("error:\n%+v", tokenResp)
err = xerrors.Errorf("failed to parse response. error = %w", err)
ac.notifyFail(err)
return err
}
log.Printf("token:\n%+v", tokenResp)

ac.AccessToken = tokenResp.AccessToken
ac.RefreshToken = tokenResp.RefreshToken
ac.Expires = tokenResp.ExpiresIn
ac.LastRefresh = time.Now()

ac.notifySuccess()

return nil
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ go 1.14

require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1.0.20200107205605-c66185887605
github.com/google/go-cmp v0.2.0
github.com/google/uuid v1.2.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v0.0.3
github.com/spf13/viper v1.3.2
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
)
20 changes: 17 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1.0.20200107205605-c66185887605 h1:BXgXhYJInnV2k1BbHUI4tRoAYEDQevlS1e0ifEzAMrU=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1.0.20200107205605-c66185887605/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
Expand Down Expand Up @@ -40,13 +44,23 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down

0 comments on commit e3d193c

Please sign in to comment.