-
-
Save giautm/8baa44ac7cee3d4c093b6f3e65853231 to your computer and use it in GitHub Desktop.
Example of using OAuth authentication with JIRA in Go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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