Skip to content

Instantly share code, notes, and snippets.

@giautm
Forked from Lupus/jira_oauth.go
Created October 27, 2021 17:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giautm/8baa44ac7cee3d4c093b6f3e65853231 to your computer and use it in GitHub Desktop.
Save giautm/8baa44ac7cee3d4c093b6f3e65853231 to your computer and use it in GitHub Desktop.
Example of using OAuth authentication with JIRA in Go
package main
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"log"
"net/url"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/andygrunwald/go-jira"
"github.com/dghubble/oauth1"
)
type TokenStorage interface {
LoadToken(ctx context.Context) (*oauth1.Token, error)
SaveToken(ctx context.Context, token *oauth1.Token) error
}
type fileTokenStorage string
var _ TokenStorage = (*fileTokenStorage)(nil)
func NewFileTokenStorage(name string) (TokenStorage, error) {
usr, err := user.Current()
if err != nil {
return nil, err
}
dir := filepath.Join(usr.HomeDir, ".credentials")
if err = os.MkdirAll(dir, 0700); err != nil {
return nil, err
}
filePath := filepath.Join(dir, url.QueryEscape(name+".json"))
return fileTokenStorage(filePath), nil
}
func (s fileTokenStorage) LoadToken(context.Context) (*oauth1.Token, error) {
f, err := os.Open(string(s))
if err != nil {
return nil, err
}
defer f.Close()
t := &oauth1.Token{}
err = json.NewDecoder(f).Decode(t)
if err != nil {
return nil, nil
}
return t, nil
}
func (s fileTokenStorage) SaveToken(_ context.Context, t *oauth1.Token) error {
fmt.Printf("Saving credential file to: %v\n", s)
f, err := os.OpenFile(string(s), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("unable to cache oauth token: %w", err)
}
defer f.Close()
return json.NewEncoder(f).Encode(t)
}
func getJIRATokenFromWeb(config *oauth1.Config) (*oauth1.Token, error) {
requestToken, requestSecret, err := config.RequestToken()
if err != nil {
return nil, fmt.Errorf("unable to get request token: %w", err)
}
authorizationURL, err := config.AuthorizationURL(requestToken)
if err != nil {
return nil, fmt.Errorf("unable to get authorization url: %w", err)
}
var code string
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\n", authorizationURL.String())
if _, err := fmt.Scan(&code); err != nil {
return nil, fmt.Errorf("unable to read authorization code: %w", err)
}
token, secret, err := config.AccessToken(requestToken, requestSecret, code)
if err != nil {
return nil, fmt.Errorf("unable to get access token: %w", err)
}
return oauth1.NewToken(token, secret), nil
}
type Config struct {
JiraURL url.URL
ConsumerKey string
PrivateKey *rsa.PrivateKey
Storage TokenStorage
}
func NewJIRAClient(ctx context.Context, c *Config) (*jira.Client, error) {
config := oauth1.Config{
ConsumerKey: c.ConsumerKey,
CallbackURL: "oob", /* for command line usage */
Endpoint: oauth1.Endpoint{
RequestTokenURL: c.JiraURL.String() + "plugins/servlet/oauth/request-token",
AuthorizeURL: c.JiraURL.String() + "plugins/servlet/oauth/authorize",
AccessTokenURL: c.JiraURL.String() + "plugins/servlet/oauth/access-token",
},
Signer: &oauth1.RSASigner{
PrivateKey: c.PrivateKey,
},
}
tok, err := c.Storage.LoadToken(ctx)
if err != nil {
tok, err = getJIRATokenFromWeb(&config)
if err != nil {
return nil, err
}
c.Storage.SaveToken(ctx, tok)
}
return jira.NewClient(config.Client(ctx, tok), c.JiraURL.String())
}
/*
$ openssl genrsa -out jira.pem 1024
$ openssl rsa -in jira.pem -pubout -out jira.pub
*/
const jiraPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----`
func main() {
jiraURL, err := url.Parse("https://sentry.io")
if err != nil {
log.Fatalf("unable to parse Jira URL: %v", err)
}
keyDERBlock, _ := pem.Decode([]byte(jiraPrivateKey))
if keyDERBlock == nil {
log.Fatal("unable to decode key PEM block")
}
if !(keyDERBlock.Type == "PRIVATE KEY" || strings.HasSuffix(keyDERBlock.Type, " PRIVATE KEY")) {
log.Fatalf("unexpected key DER block type: %s", keyDERBlock.Type)
}
privateKey, err := x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes)
if err != nil {
log.Fatalf("unable to parse PKCS1 private key: %v", err)
}
storage, err := NewFileTokenStorage(jiraURL.Host)
if err != nil {
log.Fatalf("unable to create file storage: %v", err)
}
jiraClient, err := NewJIRAClient(context.Background(), &Config{
ConsumerKey: "demo-consumer-key",
JiraURL: *jiraURL,
PrivateKey: privateKey,
Storage: storage,
})
if err != nil {
log.Fatal(err)
}
issue, _, err := jiraClient.Issue.Get("PD-1972", nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary)
fmt.Printf("Type: %s\n", issue.Fields.Type.Name)
fmt.Printf("Priority: %s\n", issue.Fields.Priority.Name)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment