Browse Source

Merge pull request #118 from matrix-org/kegan/realm-docs

Add realm documentation
kegan/guggy-tests
Kegsay 8 years ago
committed by GitHub
parent
commit
38350ede7f
  1. 103
      README.md
  2. 125
      src/github.com/matrix-org/go-neb/realms/github/github.go
  3. 182
      src/github.com/matrix-org/go-neb/realms/jira/jira.go
  4. 10
      src/github.com/matrix-org/go-neb/services/github/github.go
  5. 12
      src/github.com/matrix-org/go-neb/services/github/github_webhook.go
  6. 28
      src/github.com/matrix-org/go-neb/services/jira/jira.go
  7. 23
      src/github.com/matrix-org/go-neb/services/jira/webhook/webhook.go

103
README.md

@ -122,7 +122,7 @@ service you're interested in for the additional keys, if any.
- [HTTP API Docs](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/api/handlers/index.html#ConfigureService.OnIncomingRequest) - [HTTP API Docs](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/api/handlers/index.html#ConfigureService.OnIncomingRequest)
- [JSON Request Body Docs](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/api/index.html#ConfigureServiceRequest) - [JSON Request Body Docs](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/api/index.html#ConfigureServiceRequest)
List of services:
List of Services:
- [Echo](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/echo/) - An example service - [Echo](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/echo/) - An example service
- [Giphy](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/giphy/) - A GIF bot - [Giphy](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/giphy/) - A GIF bot
- [Github](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/github/) - A Github bot - [Github](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/github/) - A Github bot
@ -138,102 +138,21 @@ Realms are how Go-NEB authenticates users on third-party websites.
- [HTTP API Docs](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/api/handlers/index.html#ConfigureAuthRealm.OnIncomingRequest) - [HTTP API Docs](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/api/handlers/index.html#ConfigureAuthRealm.OnIncomingRequest)
- [JSON Request Body Docs](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/api/index.html#ConfigureAuthRealmRequest) - [JSON Request Body Docs](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/api/index.html#ConfigureAuthRealmRequest)
### Github Realm
This has the `Type` of `github`. To set up this realm:
```bash
curl -X POST localhost:4050/admin/configureAuthRealm --data-binary '{
"ID": "mygithubrealm",
"Type": "github",
"Config": {
"ClientSecret": "YOUR_CLIENT_SECRET",
"ClientID": "YOUR_CLIENT_ID",
"StarterLink": "https://example.com/requestGithubOAuthToken"
}
}'
```
- `ClientSecret`: Your Github application client secret
- `ClientID`: Your Github application client ID
- `StarterLink`: Optional. If supplied, `!github` commands will return this link whenever someone is prompted to login to Github.
#### Github authentication
Once you have configured a Github realm, you can associate any Matrix user ID with any Github user. To do this:
```bash
curl -X POST localhost:4050/admin/requestAuthSession --data-binary '{
"RealmID": "mygithubrealm",
"UserID": "@real_matrix_user:localhost",
"Config": {
"RedirectURL": "https://optional-url.com/to/redirect/to/after/auth"
}
}'
```
- `UserID`: The Matrix user ID to associate with.
- `RedirectURL`: Optional. The URL to redirect to after authentication.
This request will return an OAuth URL:
```json
{
"URL": "https://github.com/login/oauth/authorize?client_id=abcdef&client_secret=acascacac...."
}
```
### Github
- [Realm configuration](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/realms/github/index.html#Realm)
Follow this link to associate this user ID with this Github account. Once this is complete, Go-NEB will have an OAuth token for this user ID and will be able to create issues as their real Github account.
#### Authentication of Matrix users
To remove this session:
* [Configuration for config file](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/realms/github/index.html#Session)
* [Configuration for HTTP](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/realms/github/index.html#Realm.RequestAuthSession)
```bash
curl -X POST localhost:4050/admin/removeAuthSession --data-binary '{
"RealmID": "mygithubrealm",
"UserID": "@real_matrix_user:localhost",
"Config": {}
}'
```
### JIRA Realm
This has the `Type` of `jira`. To set up this realm:
```bash
curl -X POST localhost:4050/admin/configureAuthRealm --data-binary '{
"ID": "jirarealm",
"Type": "jira",
"Config": {
"JIRAEndpoint": "matrix.org/jira/",
"ConsumerName": "goneb",
"ConsumerKey": "goneb",
"ConsumerSecret": "random_long_string",
"PrivateKeyPEM": "-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA39UhbOvQHEkBP9fGnhU+eSObTWBDGWygVYzbcONOlqEOTJUN\r\n8gmnellWqJO45S4jB1vLLnuXiHqEWnmaShIvbUem3QnDDqghu0gfqXHMlQr5R8ZP\r\norTt1F2idWy1wk5rVXeLKSG7uriYhDVOVS69WuefoW5v55b5YZV283v2jROjxHuj\r\ngAsJA7k6tvpYiSXApUl6YHmECfBoiwG9bwItkHwhZ\/fG9i4H8\/aOyr3WlaWbVeKX\r\n+m38lmYZvzQFRAk5ab1vzCGz4cyc\r\nTk2qmZpcjHRd1ijcOkgC23KF8lHWF5Zx0tySR+DWL1JeGm8NJxKMRJZuE8MIkJYF\r\nryE7kjspNItk6npkA3\/A4PWwElhddI4JpiuK+29mMNipRcYYy9e0vH\/igejv7ayd\r\nPLCRMQKBgBDSNWlZT0nNd2DXVqTW9p+MG72VKhDgmEwFB1acOw0lpu1XE8R1wmwG\r\nZRl\/xzri3LOW2Gpc77xu6fs3NIkzQw3v1ifYhX3OrVsCIRBbDjPQI3yYjkhGx24s\r\nVhhZ5S\/TkGk3Kw59bDC6KGqAuQAwX9req2l1NiuNaPU9rE7tf6Bk\r\n-----END RSA PRIVATE KEY-----"
}
}'
```
- `JIRAEndpoint`: The base URL of the JIRA installation you wish to talk to.
- `ConsumerName`: The desired "Consumer Name" field of the "Application Links" admin page on JIRA. Generally this is the name of the service. Users will need to enter this string into their JIRA admin web form.
- `ConsumerKey`: The desired "Consumer Key" field of the "Application Links" admin page on JIRA. Generally this is the name of the service. Users will need to enter this string into their JIRA admin web form.
- `ConsumerSecret`: The desired "Consumer Secret" field of the "Application Links" admin page on JIRA. This should be a random long string. Users will need to enter this string into their JIRA admin web form.
- `PrivateKeyPEM`: A string which contains the private key for performing OAuth 1.0 requests. This MUST be in PEM format. It must NOT have a password. Go-NEB will convert this into a **public** key in PEM format and return this to users. Users will need to enter the public key into their JIRA admin web form.
- `StarterLink`: Optional. If supplied, `!jira` commands will return this link whenever someone is prompted to login to JIRA.
To generate a private key PEM: (JIRA does not support bit lengths >2048)
```bash
openssl genrsa -out privkey.pem 2048
cat privkey.pem
```
#### JIRA authentication
### JIRA
- [Realm configuration](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/realms/jira/index.html#Realm)
```
curl -X POST localhost:4050/admin/requestAuthSession --data-binary '{
"RealmID": "jirarealm",
"UserID": "@example:localhost",
"Config": {
"RedirectURL": "https://optional-url.com/to/redirect/to/after/auth"
}
}'
```
#### Authentication of Matrix users
* [Configuration for config file](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/realms/jira/index.html#Session)
* [Configuration for HTTP](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/realms/jira/index.html#Realm.RequestAuthSession)
Returns:
```json
{
"URL":"https://jira.somewhere.com/plugins/servlet/oauth/authorize?oauth_token=7yeuierbgweguiegrTbOT"
}
```
# Developing # Developing
There's a bunch more tools this project uses when developing in order to do There's a bunch more tools this project uses when developing in order to do

125
src/github.com/matrix-org/go-neb/realms/github/github.go

@ -1,48 +1,76 @@
package realms
// Package github implements OAuth2 support for github.com
package github
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"io/ioutil"
"net/http"
"net/url"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/google/go-github/github" "github.com/google/go-github/github"
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/services/github/client" "github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"io/ioutil"
"net/http"
"net/url"
) )
// GithubRealm can handle OAuth processes with github.com
type GithubRealm struct {
id string
redirectURL string
// RealmType of the Github Realm
const RealmType = "github"
// Realm can handle OAuth processes with github.com
//
// Example request:
// {
// "ClientSecret": "YOUR_CLIENT_SECRET",
// "ClientID": "YOUR_CLIENT_ID"
// }
type Realm struct {
id string
redirectURL string
// The client secret for this Github application.
ClientSecret string ClientSecret string
ClientID string
StarterLink string
// The client ID for this Github application.
ClientID string
// Optional. The URL to redirect the client to after authentication.
StarterLink string
} }
// GithubSession represents an authenticated github session
type GithubSession struct {
// The client-supplied URL to redirect them to after the auth process is complete.
ClientsRedirectURL string
// AccessToken is the github access token for the user
AccessToken string
// Scopes are the set of *ALLOWED* scopes (which may not be the same as the requested scopes)
Scopes string
// Session represents an authenticated github session
type Session struct {
id string id string
userID string userID string
realmID string realmID string
// AccessToken is the github access token for the user
AccessToken string
// Scopes are the set of *ALLOWED* scopes (which may not be the same as the requested scopes)
Scopes string
// Optional. The client-supplied URL to redirect them to after the auth process is complete.
ClientsRedirectURL string
}
// AuthRequest is a request for authenticating with github.com
type AuthRequest struct {
// Optional. The URL to redirect to after authentication.
RedirectURL string
}
// AuthResponse is a response to an AuthRequest.
type AuthResponse struct {
// The URL to visit to perform OAuth on github.com
URL string
} }
// Authenticated returns true if the user has completed the auth process // Authenticated returns true if the user has completed the auth process
func (s *GithubSession) Authenticated() bool {
func (s *Session) Authenticated() bool {
return s.AccessToken != "" return s.AccessToken != ""
} }
// Info returns a list of possible repositories that this session can integrate with. // Info returns a list of possible repositories that this session can integrate with.
func (s *GithubSession) Info() interface{} {
func (s *Session) Info() interface{} {
logger := log.WithFields(log.Fields{ logger := log.WithFields(log.Fields{
"user_id": s.userID, "user_id": s.userID,
"realm_id": s.realmID, "realm_id": s.realmID,
@ -72,9 +100,9 @@ func (s *GithubSession) Info() interface{} {
break break
} }
opts.ListOptions.Page = resp.NextPage opts.ListOptions.Page = resp.NextPage
logger.Print("GithubSession.Info() Next => ", resp.NextPage)
logger.Print("Session.Info() Next => ", resp.NextPage)
} }
logger.Print("GithubSession.Info() Returning ", len(repos), " repos")
logger.Print("Session.Info() Returning ", len(repos), " repos")
return struct { return struct {
Repos []client.TrimmedRepository Repos []client.TrimmedRepository
@ -82,42 +110,53 @@ func (s *GithubSession) Info() interface{} {
} }
// UserID returns the user_id who authorised with Github // UserID returns the user_id who authorised with Github
func (s *GithubSession) UserID() string {
func (s *Session) UserID() string {
return s.userID return s.userID
} }
// RealmID returns the realm ID of the realm which performed the authentication // RealmID returns the realm ID of the realm which performed the authentication
func (s *GithubSession) RealmID() string {
func (s *Session) RealmID() string {
return s.realmID return s.realmID
} }
// ID returns the session ID // ID returns the session ID
func (s *GithubSession) ID() string {
func (s *Session) ID() string {
return s.id return s.id
} }
// ID returns the realm ID // ID returns the realm ID
func (r *GithubRealm) ID() string {
func (r *Realm) ID() string {
return r.id return r.id
} }
// Type is github // Type is github
func (r *GithubRealm) Type() string {
return "github"
func (r *Realm) Type() string {
return RealmType
} }
// Init does nothing. // Init does nothing.
func (r *GithubRealm) Init() error {
func (r *Realm) Init() error {
return nil return nil
} }
// Register does nothing. // Register does nothing.
func (r *GithubRealm) Register() error {
func (r *Realm) Register() error {
return nil return nil
} }
// RequestAuthSession generates an OAuth2 URL for this user to auth with github via. // RequestAuthSession generates an OAuth2 URL for this user to auth with github via.
func (r *GithubRealm) RequestAuthSession(userID string, req json.RawMessage) interface{} {
// The request body is of type "github.AuthRequest". The response is of type "github.AuthResponse".
//
// Request example:
// {
// "RedirectURL": "https://optional-url.com/to/redirect/to/after/auth"
// }
//
// Response example:
// {
// "URL": "https://github.com/login/oauth/authorize?client_id=abcdef&client_secret=acascacac...."
// }
func (r *Realm) RequestAuthSession(userID string, req json.RawMessage) interface{} {
state, err := randomString(10) state, err := randomString(10)
if err != nil { if err != nil {
log.WithError(err).Print("Failed to generate state param") log.WithError(err).Print("Failed to generate state param")
@ -132,16 +171,14 @@ func (r *GithubRealm) RequestAuthSession(userID string, req json.RawMessage) int
q.Set("redirect_uri", r.redirectURL) q.Set("redirect_uri", r.redirectURL)
q.Set("scope", "admin:repo_hook,admin:org_hook,repo") q.Set("scope", "admin:repo_hook,admin:org_hook,repo")
u.RawQuery = q.Encode() u.RawQuery = q.Encode()
session := &GithubSession{
session := &Session{
id: state, // key off the state for redirects id: state, // key off the state for redirects
userID: userID, userID: userID,
realmID: r.ID(), realmID: r.ID(),
} }
// check if they supplied a redirect URL // check if they supplied a redirect URL
var reqBody struct {
RedirectURL string
}
var reqBody AuthRequest
if err = json.Unmarshal(req, &reqBody); err != nil { if err = json.Unmarshal(req, &reqBody); err != nil {
log.WithError(err).Print("Failed to decode request body") log.WithError(err).Print("Failed to decode request body")
return nil return nil
@ -158,13 +195,11 @@ func (r *GithubRealm) RequestAuthSession(userID string, req json.RawMessage) int
return nil return nil
} }
return &struct {
URL string
}{u.String()}
return &AuthResponse{u.String()}
} }
// OnReceiveRedirect processes OAuth redirect requests from Github // OnReceiveRedirect processes OAuth redirect requests from Github
func (r *GithubRealm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request) {
func (r *Realm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request) {
// parse out params from the request // parse out params from the request
code := req.URL.Query().Get("code") code := req.URL.Query().Get("code")
state := req.URL.Query().Get("state") state := req.URL.Query().Get("state")
@ -183,7 +218,7 @@ func (r *GithubRealm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request
failWith(logger, w, 400, "Provided ?state= param is not recognised.", err) failWith(logger, w, 400, "Provided ?state= param is not recognised.", err)
return return
} }
ghSession, ok := session.(*GithubSession)
ghSession, ok := session.(*Session)
if !ok { if !ok {
failWith(logger, w, 500, "Unexpected session found.", nil) failWith(logger, w, 500, "Unexpected session found.", nil)
return return
@ -228,7 +263,7 @@ func (r *GithubRealm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request
) )
} }
func (r *GithubRealm) redirectOr(w http.ResponseWriter, code int, msg string, logger *log.Entry, ghSession *GithubSession) {
func (r *Realm) redirectOr(w http.ResponseWriter, code int, msg string, logger *log.Entry, ghSession *Session) {
if ghSession.ClientsRedirectURL != "" { if ghSession.ClientsRedirectURL != "" {
w.Header().Set("Location", ghSession.ClientsRedirectURL) w.Header().Set("Location", ghSession.ClientsRedirectURL)
w.WriteHeader(302) w.WriteHeader(302)
@ -239,9 +274,9 @@ func (r *GithubRealm) redirectOr(w http.ResponseWriter, code int, msg string, lo
} }
} }
// AuthSession returns a GithubSession for this user
func (r *GithubRealm) AuthSession(id, userID, realmID string) types.AuthSession {
return &GithubSession{
// AuthSession returns a Github Session for this user
func (r *Realm) AuthSession(id, userID, realmID string) types.AuthSession {
return &Session{
id: id, id: id,
userID: userID, userID: userID,
realmID: realmID, realmID: realmID,
@ -267,6 +302,6 @@ func randomString(length int) (string, error) {
func init() { func init() {
types.RegisterAuthRealm(func(realmID, redirectURL string) types.AuthRealm { types.RegisterAuthRealm(func(realmID, redirectURL string) types.AuthRealm {
return &GithubRealm{id: realmID, redirectURL: redirectURL}
return &Realm{id: realmID, redirectURL: redirectURL}
}) })
} }

182
src/github.com/matrix-org/go-neb/realms/jira/jira.go

@ -1,4 +1,5 @@
package realms
// Package jira implements OAuth1.0a support for arbitrary JIRA installations.
package jira
import ( import (
"crypto/rsa" "crypto/rsa"
@ -8,84 +9,145 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"net/http"
"strings"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/andygrunwald/go-jira"
jira "github.com/andygrunwald/go-jira"
"github.com/dghubble/oauth1" "github.com/dghubble/oauth1"
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/realms/jira/urls" "github.com/matrix-org/go-neb/realms/jira/urls"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"golang.org/x/net/context" "golang.org/x/net/context"
"net/http"
"strings"
) )
// JIRARealm is an AuthRealm which can process JIRA installations
type JIRARealm struct {
id string
redirectURL string
privateKey *rsa.PrivateKey
JIRAEndpoint string
Server string // clobbered based on /serverInfo request
Version string // clobbered based on /serverInfo request
ConsumerName string
ConsumerKey string
// RealmType of the JIRA realm
const RealmType = "jira"
// Realm is an AuthRealm which can process JIRA installations.
//
// Example request:
// {
// "JIRAEndpoint": "matrix.org/jira/",
// "ConsumerName": "goneb",
// "ConsumerKey": "goneb",
// "ConsumerSecret": "random_long_string",
// "PrivateKeyPEM": "-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA39UhbOvQHEkBP9fGnhU+eSObTAwX9req2l1NiuNaPU9rE7tf6Bk\r\n-----END RSA PRIVATE KEY-----"
// }
type Realm struct {
id string
redirectURL string
privateKey *rsa.PrivateKey
// The HTTPS URL of the JIRA installation to authenticate with.
JIRAEndpoint string
// The desired "Consumer Name" field of the "Application Links" admin page on JIRA.
// Generally this is the name of the service. Users will need to enter this string
// into their JIRA admin web form.
ConsumerName string
// The desired "Consumer Key" field of the "Application Links" admin page on JIRA.
// Generally this is the name of the service. Users will need to enter this string
// into their JIRA admin web form.
ConsumerKey string
// The desired "Consumer Secret" field of the "Application Links" admin page on JIRA.
// This should be a random long string. Users will need to enter this string into
// their JIRA admin web form.
ConsumerSecret string ConsumerSecret string
PublicKeyPEM string // clobbered based on PrivateKeyPEM
PrivateKeyPEM string
HasWebhook bool // clobbered based on NEB
StarterLink string
// A string which contains the private key for performing OAuth 1.0 requests.
// This MUST be in PEM format. It must NOT have a password. Go-NEB will convert this
// into a public key in PEM format and return this to users. Users will need to enter
// the *public* key into their JIRA admin web form.
//
// To generate a private key PEM: (JIRA does not support bit lengths >2048):
// $ openssl genrsa -out privkey.pem 2048
// $ cat privkey.pem
PrivateKeyPEM string
// Optional. If supplied, !jira commands will return this link whenever someone is
// prompted to login to JIRA.
StarterLink string
// The server name of the JIRA installation from /serverInfo.
// This is an informational field populated by Go-NEB post-creation.
Server string
// The JIRA version string from /serverInfo.
// This is an informational field populated by Go-NEB post-creation.
Version string
// The public key for the given private key. This is populated by Go-NEB.
PublicKeyPEM string
// Internal field. True if this realm has already registered a webhook with the JIRA installation.
HasWebhook bool
} }
// JIRASession represents a single authentication session between a user and a JIRA endpoint.
// Session represents a single authentication session between a user and a JIRA endpoint.
// The endpoint is dictated by the realm ID. // The endpoint is dictated by the realm ID.
type JIRASession struct {
id string // request token
userID string
realmID string
RequestSecret string
AccessToken string
AccessSecret string
ClientsRedirectURL string // where to redirect the client to after auth
type Session struct {
id string // request token
userID string
realmID string
// Configuration fields
// The secret obtained when requesting an authentication session with JIRA.
RequestSecret string
// A JIRA access token for a Matrix user ID.
AccessToken string
// A JIRA access secret for a Matrix user ID.
AccessSecret string
// Optional. The URL to redirect the client to after authentication.
ClientsRedirectURL string
}
// AuthRequest is a request for authenticating with JIRA
type AuthRequest struct {
// Optional. The URL to redirect to after authentication.
RedirectURL string
}
// AuthResponse is a response to an AuthRequest.
type AuthResponse struct {
// The URL to visit to perform OAuth on this JIRA installation.
URL string
} }
// Authenticated returns true if the user has completed the auth process // Authenticated returns true if the user has completed the auth process
func (s *JIRASession) Authenticated() bool {
func (s *Session) Authenticated() bool {
return s.AccessToken != "" && s.AccessSecret != "" return s.AccessToken != "" && s.AccessSecret != ""
} }
// Info returns nothing // Info returns nothing
func (s *JIRASession) Info() interface{} {
func (s *Session) Info() interface{} {
return nil return nil
} }
// UserID returns the ID of the user performing the authentication. // UserID returns the ID of the user performing the authentication.
func (s *JIRASession) UserID() string {
func (s *Session) UserID() string {
return s.userID return s.userID
} }
// RealmID returns the JIRA realm ID which created this session. // RealmID returns the JIRA realm ID which created this session.
func (s *JIRASession) RealmID() string {
func (s *Session) RealmID() string {
return s.realmID return s.realmID
} }
// ID returns the OAuth1 request_token which is used when looking up sessions in the redirect // ID returns the OAuth1 request_token which is used when looking up sessions in the redirect
// handler. // handler.
func (s *JIRASession) ID() string {
func (s *Session) ID() string {
return s.id return s.id
} }
// ID returns the ID of this JIRA realm. // ID returns the ID of this JIRA realm.
func (r *JIRARealm) ID() string {
func (r *Realm) ID() string {
return r.id return r.id
} }
// Type returns the type of realm this is. // Type returns the type of realm this is.
func (r *JIRARealm) Type() string {
return "jira"
func (r *Realm) Type() string {
return RealmType
} }
// Init initialises the private key for this JIRA realm. // Init initialises the private key for this JIRA realm.
func (r *JIRARealm) Init() error {
func (r *Realm) Init() error {
if err := r.parsePrivateKey(); err != nil { if err := r.parsePrivateKey(); err != nil {
log.WithError(err).Print("Failed to parse private key") log.WithError(err).Print("Failed to parse private key")
return err return err
@ -101,7 +163,7 @@ func (r *JIRARealm) Init() error {
} }
// Register is called when this realm is being created from an external entity // Register is called when this realm is being created from an external entity
func (r *JIRARealm) Register() error {
func (r *Realm) Register() error {
if r.ConsumerName == "" || r.ConsumerKey == "" || r.ConsumerSecret == "" || r.PrivateKeyPEM == "" { if r.ConsumerName == "" || r.ConsumerKey == "" || r.ConsumerSecret == "" || r.PrivateKeyPEM == "" {
return errors.New("ConsumerName, ConsumerKey, ConsumerSecret, PrivateKeyPEM must be specified.") return errors.New("ConsumerName, ConsumerKey, ConsumerSecret, PrivateKeyPEM must be specified.")
} }
@ -130,14 +192,22 @@ func (r *JIRARealm) Register() error {
return nil return nil
} }
// RequestAuthSession is called by a user wishing to auth with this JIRA realm
func (r *JIRARealm) RequestAuthSession(userID string, req json.RawMessage) interface{} {
// RequestAuthSession is called by a user wishing to auth with this JIRA realm.
// The request body is of type "jira.AuthRequest". Returns a "jira.AuthResponse".
//
// Request example:
// {
// "RedirectURL": "https://somewhere.somehow"
// }
// Response example:
// {
// "URL": "https://jira.somewhere.com/plugins/servlet/oauth/authorize?oauth_token=7yeuierbgweguiegrTbOT"
// }
func (r *Realm) RequestAuthSession(userID string, req json.RawMessage) interface{} {
logger := log.WithField("jira_url", r.JIRAEndpoint) logger := log.WithField("jira_url", r.JIRAEndpoint)
// check if they supplied a redirect URL // check if they supplied a redirect URL
var reqBody struct {
RedirectURL string
}
var reqBody AuthRequest
if err := json.Unmarshal(req, &reqBody); err != nil { if err := json.Unmarshal(req, &reqBody); err != nil {
log.WithError(err).Print("Failed to decode request body") log.WithError(err).Print("Failed to decode request body")
return nil return nil
@ -156,7 +226,7 @@ func (r *JIRARealm) RequestAuthSession(userID string, req json.RawMessage) inter
return nil return nil
} }
_, err = database.GetServiceDB().StoreAuthSession(&JIRASession{
_, err = database.GetServiceDB().StoreAuthSession(&Session{
id: reqToken, id: reqToken,
userID: userID, userID: userID,
realmID: r.id, realmID: r.id,
@ -168,13 +238,11 @@ func (r *JIRARealm) RequestAuthSession(userID string, req json.RawMessage) inter
return nil return nil
} }
return &struct {
URL string
}{authURL.String()}
return &AuthResponse{authURL.String()}
} }
// OnReceiveRedirect is called when JIRA installations redirect back to NEB // OnReceiveRedirect is called when JIRA installations redirect back to NEB
func (r *JIRARealm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request) {
func (r *Realm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request) {
logger := log.WithField("jira_url", r.JIRAEndpoint) logger := log.WithField("jira_url", r.JIRAEndpoint)
requestToken, verifier, err := oauth1.ParseAuthorizationCallback(req) requestToken, verifier, err := oauth1.ParseAuthorizationCallback(req)
@ -190,7 +258,7 @@ func (r *JIRARealm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request)
failWith(logger, w, 400, "Unrecognised request token", err) failWith(logger, w, 400, "Unrecognised request token", err)
return return
} }
jiraSession, ok := session.(*JIRASession)
jiraSession, ok := session.(*Session)
if !ok { if !ok {
failWith(logger, w, 500, "Unexpected session type found.", nil) failWith(logger, w, 500, "Unexpected session type found.", nil)
return return
@ -230,8 +298,8 @@ func (r *JIRARealm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request)
} }
// AuthSession returns a JIRASession with the given parameters // AuthSession returns a JIRASession with the given parameters
func (r *JIRARealm) AuthSession(id, userID, realmID string) types.AuthSession {
return &JIRASession{
func (r *Realm) AuthSession(id, userID, realmID string) types.AuthSession {
return &Session{
id: id, id: id,
userID: userID, userID: userID,
realmID: realmID, realmID: realmID,
@ -242,7 +310,7 @@ func (r *JIRARealm) AuthSession(id, userID, realmID string) types.AuthSession {
// An authenticated client for userID will be used if one exists, else an // An authenticated client for userID will be used if one exists, else an
// unauthenticated client will be used, which may not be able to see the complete list // unauthenticated client will be used, which may not be able to see the complete list
// of projects. // of projects.
func (r *JIRARealm) ProjectKeyExists(userID, projectKey string) (bool, error) {
func (r *Realm) ProjectKeyExists(userID, projectKey string) (bool, error) {
cli, err := r.JIRAClient(userID, true) cli, err := r.JIRAClient(userID, true)
if err != nil { if err != nil {
return false, err return false, err
@ -276,7 +344,7 @@ func (r *JIRARealm) ProjectKeyExists(userID, projectKey string) (bool, error) {
// JIRAClient returns an authenticated jira.Client for the given userID. Returns an unauthenticated // JIRAClient returns an authenticated jira.Client for the given userID. Returns an unauthenticated
// client if allowUnauth is true and no authenticated session is found, else returns an error. // client if allowUnauth is true and no authenticated session is found, else returns an error.
func (r *JIRARealm) JIRAClient(userID string, allowUnauth bool) (*jira.Client, error) {
func (r *Realm) JIRAClient(userID string, allowUnauth bool) (*jira.Client, error) {
// Check if user has an auth session. // Check if user has an auth session.
session, err := database.GetServiceDB().LoadAuthSessionByUser(r.id, userID) session, err := database.GetServiceDB().LoadAuthSessionByUser(r.id, userID)
if err != nil { if err != nil {
@ -289,9 +357,9 @@ func (r *JIRARealm) JIRAClient(userID string, allowUnauth bool) (*jira.Client, e
return nil, err return nil, err
} }
jsession, ok := session.(*JIRASession)
jsession, ok := session.(*Session)
if !ok { if !ok {
return nil, errors.New("Failed to cast user session to a JIRASession")
return nil, errors.New("Failed to cast user session to a Session")
} }
// Make sure they finished the auth process // Make sure they finished the auth process
if jsession.AccessSecret == "" || jsession.AccessToken == "" { if jsession.AccessSecret == "" || jsession.AccessToken == "" {
@ -310,7 +378,7 @@ func (r *JIRARealm) JIRAClient(userID string, allowUnauth bool) (*jira.Client, e
return jira.NewClient(httpClient, r.JIRAEndpoint) return jira.NewClient(httpClient, r.JIRAEndpoint)
} }
func (r *JIRARealm) parsePrivateKey() error {
func (r *Realm) parsePrivateKey() error {
if r.privateKey != nil { if r.privateKey != nil {
return nil return nil
} }
@ -327,7 +395,7 @@ func (r *JIRARealm) parsePrivateKey() error {
return nil return nil
} }
func (r *JIRARealm) oauth1Config(jiraBaseURL string) *oauth1.Config {
func (r *Realm) oauth1Config(jiraBaseURL string) *oauth1.Config {
return &oauth1.Config{ return &oauth1.Config{
ConsumerKey: r.ConsumerKey, ConsumerKey: r.ConsumerKey,
ConsumerSecret: r.ConsumerSecret, ConsumerSecret: r.ConsumerSecret,
@ -402,6 +470,6 @@ func failWith(logger *log.Entry, w http.ResponseWriter, code int, msg string, er
func init() { func init() {
types.RegisterAuthRealm(func(realmID, redirectURL string) types.AuthRealm { types.RegisterAuthRealm(func(realmID, redirectURL string) types.AuthRealm {
return &JIRARealm{id: realmID, redirectURL: redirectURL}
return &Realm{id: realmID, redirectURL: redirectURL}
}) })
} }

10
src/github.com/matrix-org/go-neb/services/github/github.go

@ -12,7 +12,7 @@ import (
"strings" "strings"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/google/go-github/github"
gogithub "github.com/google/go-github/github"
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/realms/github" "github.com/matrix-org/go-neb/realms/github"
@ -61,7 +61,7 @@ func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interfa
if err != nil { if err != nil {
return nil, err return nil, err
} }
ghRealm, ok := r.(*realms.GithubRealm)
ghRealm, ok := r.(*github.Realm)
if !ok { if !ok {
return nil, fmt.Errorf("Failed to cast realm %s into a GithubRealm", s.RealmID) return nil, fmt.Errorf("Failed to cast realm %s into a GithubRealm", s.RealmID)
} }
@ -115,7 +115,7 @@ func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interfa
title = &joinedTitle title = &joinedTitle
} }
issue, res, err := cli.Issues.Create(ownerRepoGroups[1], ownerRepoGroups[2], &github.IssueRequest{
issue, res, err := cli.Issues.Create(ownerRepoGroups[1], ownerRepoGroups[2], &gogithub.IssueRequest{
Title: title, Title: title,
Body: desc, Body: desc,
}) })
@ -268,7 +268,7 @@ func (s *Service) defaultRepo(roomID string) string {
return defaultRepo return defaultRepo
} }
func (s *Service) githubClientFor(userID string, allowUnauth bool) *github.Client {
func (s *Service) githubClientFor(userID string, allowUnauth bool) *gogithub.Client {
token, err := getTokenForUser(s.RealmID, userID) token, err := getTokenForUser(s.RealmID, userID)
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -300,7 +300,7 @@ func getTokenForUser(realmID, userID string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
ghSession, ok := session.(*realms.GithubSession)
ghSession, ok := session.(*github.Session)
if !ok { if !ok {
return "", fmt.Errorf("Session is not a github session: %s", session.ID()) return "", fmt.Errorf("Session is not a github session: %s", session.ID())
} }

12
src/github.com/matrix-org/go-neb/services/github/github_webhook.go

@ -7,7 +7,7 @@ import (
"strings" "strings"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/google/go-github/github"
gogithub "github.com/google/go-github/github"
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/services/github/client" "github.com/matrix-org/go-neb/services/github/client"
@ -285,7 +285,7 @@ func (s *WebhookService) repoList() []string {
return repos return repos
} }
func (s *WebhookService) createHook(cli *github.Client, ownerRepo string) error {
func (s *WebhookService) createHook(cli *gogithub.Client, ownerRepo string) error {
o := strings.Split(ownerRepo, "/") o := strings.Split(ownerRepo, "/")
owner := o[0] owner := o[0]
repo := o[1] repo := o[1]
@ -299,14 +299,14 @@ func (s *WebhookService) createHook(cli *github.Client, ownerRepo string) error
cfg["secret"] = s.SecretToken cfg["secret"] = s.SecretToken
} }
events := []string{"push", "pull_request", "issues", "issue_comment", "pull_request_review_comment"} events := []string{"push", "pull_request", "issues", "issue_comment", "pull_request_review_comment"}
_, res, err := cli.Repositories.CreateHook(owner, repo, &github.Hook{
_, res, err := cli.Repositories.CreateHook(owner, repo, &gogithub.Hook{
Name: &name, Name: &name,
Config: cfg, Config: cfg,
Events: events, Events: events,
}) })
if res.StatusCode == 422 { if res.StatusCode == 422 {
errResponse, ok := err.(*github.ErrorResponse)
errResponse, ok := err.(*gogithub.ErrorResponse)
if !ok { if !ok {
return err return err
} }
@ -341,7 +341,7 @@ func (s *WebhookService) deleteHook(owner, repo string) error {
if err != nil { if err != nil {
return err return err
} }
var hook *github.Hook
var hook *gogithub.Hook
for _, h := range hooks { for _, h := range hooks {
if h.Config["url"] == nil { if h.Config["url"] == nil {
logger.Print("Ignoring nil config.url") logger.Print("Ignoring nil config.url")
@ -396,7 +396,7 @@ func sameRepos(a *WebhookService, b *WebhookService) bool {
return true return true
} }
func (s *WebhookService) githubClientFor(userID string, allowUnauth bool) *github.Client {
func (s *WebhookService) githubClientFor(userID string, allowUnauth bool) *gogithub.Client {
token, err := getTokenForUser(s.RealmID, userID) token, err := getTokenForUser(s.RealmID, userID)
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{

28
src/github.com/matrix-org/go-neb/services/jira/jira.go

@ -13,7 +13,7 @@ import (
"strings" "strings"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
jira "github.com/andygrunwald/go-jira"
gojira "github.com/andygrunwald/go-jira"
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/realms/jira" "github.com/matrix-org/go-neb/realms/jira"
@ -81,7 +81,7 @@ func (s *Service) Register(oldService types.Service, client *matrix.Client) erro
if err != nil { if err != nil {
return err return err
} }
jrealm, ok := realm.(*realms.JIRARealm)
jrealm, ok := realm.(*jira.Realm)
if !ok { if !ok {
return errors.New("Realm ID doesn't map to a JIRA realm") return errors.New("Realm ID doesn't map to a JIRA realm")
} }
@ -123,15 +123,15 @@ func (s *Service) cmdJiraCreate(roomID, userID string, args []string) (interface
return nil, errors.New("No known project exists with that project key.") return nil, errors.New("No known project exists with that project key.")
} }
iss := jira.Issue{
Fields: &jira.IssueFields{
iss := gojira.Issue{
Fields: &gojira.IssueFields{
Summary: title, Summary: title,
Description: desc, Description: desc,
Project: jira.Project{
Project: gojira.Project{
Key: pkey, Key: pkey,
}, },
// FIXME: This may vary depending on the JIRA install! // FIXME: This may vary depending on the JIRA install!
Type: jira.IssueType{
Type: gojira.IssueType{
Name: "Bug", Name: "Bug",
}, },
}, },
@ -192,10 +192,10 @@ func (s *Service) expandIssue(roomID, userID string, issueKeyGroups []string) in
}).Print("Failed to load realm") }).Print("Failed to load realm")
return nil return nil
} }
jrealm, ok := r.(*realms.JIRARealm)
jrealm, ok := r.(*jira.Realm)
if !ok { if !ok {
logger.WithField("realm_id", realmID).Print( logger.WithField("realm_id", realmID).Print(
"Realm cannot be typecast to JIRARealm",
"Realm cannot be typecast to jira.Realm",
) )
} }
logger.WithFields(log.Fields{ logger.WithFields(log.Fields{
@ -323,7 +323,7 @@ func (s *Service) realmIDForProject(roomID, projectKey string) string {
return "" return ""
} }
func (s *Service) projectToRealm(userID, pkey string) (*realms.JIRARealm, error) {
func (s *Service) projectToRealm(userID, pkey string) (*jira.Realm, error) {
// We don't know which JIRA installation this project maps to, so: // We don't know which JIRA installation this project maps to, so:
// - Get all known JIRA realms and f.e query their endpoints with the // - Get all known JIRA realms and f.e query their endpoints with the
// given user ID's credentials (so if it is a private project they // given user ID's credentials (so if it is a private project they
@ -341,13 +341,13 @@ func (s *Service) projectToRealm(userID, pkey string) (*realms.JIRARealm, error)
return nil, err return nil, err
} }
// typecast and move ones which the user has authed with to the front of the queue // typecast and move ones which the user has authed with to the front of the queue
var queue []*realms.JIRARealm
var unauthRealms []*realms.JIRARealm
var queue []*jira.Realm
var unauthRealms []*jira.Realm
for _, r := range knownRealms { for _, r := range knownRealms {
jrealm, ok := r.(*realms.JIRARealm)
jrealm, ok := r.(*jira.Realm)
if !ok { if !ok {
logger.WithField("realm_id", r.ID()).Print( logger.WithField("realm_id", r.ID()).Print(
"Failed to type-cast 'jira' type realm into JIRARealm",
"Failed to type-cast 'jira' type realm into jira.Realm",
) )
continue continue
} }
@ -402,7 +402,7 @@ func projectsAndRealmsToTrack(s *Service) map[string][]string {
return ridsToProjects return ridsToProjects
} }
func htmlSummaryForIssue(issue *jira.Issue) string {
func htmlSummaryForIssue(issue *gojira.Issue) string {
// form a summary of the issue being affected e.g: // form a summary of the issue being affected e.g:
// "Flibble Wibble [P1, In Progress]" // "Flibble Wibble [P1, In Progress]"
status := html.EscapeString(issue.Fields.Status.Name) status := html.EscapeString(issue.Fields.Status.Name)

23
src/github.com/matrix-org/go-neb/services/jira/webhook/webhook.go

@ -3,13 +3,14 @@ package webhook
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"strings"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/andygrunwald/go-jira"
gojira "github.com/andygrunwald/go-jira"
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/errors" "github.com/matrix-org/go-neb/errors"
"github.com/matrix-org/go-neb/realms/jira" "github.com/matrix-org/go-neb/realms/jira"
"net/http"
"strings"
) )
type jiraWebhook struct { type jiraWebhook struct {
@ -24,14 +25,14 @@ type jiraWebhook struct {
// Event represents an incoming JIRA webhook event // Event represents an incoming JIRA webhook event
type Event struct { type Event struct {
WebhookEvent string `json:"webhookEvent"`
Timestamp int64 `json:"timestamp"`
User jira.User `json:"user"`
Issue jira.Issue `json:"issue"`
WebhookEvent string `json:"webhookEvent"`
Timestamp int64 `json:"timestamp"`
User gojira.User `json:"user"`
Issue gojira.Issue `json:"issue"`
} }
// RegisterHook checks to see if this user is allowed to track the given projects and then tracks them. // RegisterHook checks to see if this user is allowed to track the given projects and then tracks them.
func RegisterHook(jrealm *realms.JIRARealm, projects []string, userID, webhookEndpointURL string) error {
func RegisterHook(jrealm *jira.Realm, projects []string, userID, webhookEndpointURL string) error {
// Tracking means that a webhook may need to be created on the remote JIRA installation. // Tracking means that a webhook may need to be created on the remote JIRA installation.
// We need to make sure that the user has permission to do this. If they don't, it may still be okay if // We need to make sure that the user has permission to do this. If they don't, it may still be okay if
// there is an existing webhook set up for this installation by someone else, *PROVIDED* that the projects // there is an existing webhook set up for this installation by someone else, *PROVIDED* that the projects
@ -117,7 +118,7 @@ func OnReceiveRequest(req *http.Request) (string, *Event, *errors.HTTPError) {
return projKey, &whe, nil return projKey, &whe, nil
} }
func createWebhook(jrealm *realms.JIRARealm, webhookEndpointURL, userID string) error {
func createWebhook(jrealm *jira.Realm, webhookEndpointURL, userID string) error {
cli, err := jrealm.JIRAClient(userID, false) cli, err := jrealm.JIRAClient(userID, false)
if err != nil { if err != nil {
return err return err
@ -152,7 +153,7 @@ func createWebhook(jrealm *realms.JIRARealm, webhookEndpointURL, userID string)
return err return err
} }
func getWebhook(cli *jira.Client, webhookEndpointURL string) (*jiraWebhook, *errors.HTTPError) {
func getWebhook(cli *gojira.Client, webhookEndpointURL string) (*jiraWebhook, *errors.HTTPError) {
req, err := cli.NewRequest("GET", "rest/webhooks/1.0/webhook", nil) req, err := cli.NewRequest("GET", "rest/webhooks/1.0/webhook", nil)
if err != nil { if err != nil {
return nil, &errors.HTTPError{err, "Failed to prepare webhook request", 500} return nil, &errors.HTTPError{err, "Failed to prepare webhook request", 500}
@ -180,7 +181,7 @@ func getWebhook(cli *jira.Client, webhookEndpointURL string) (*jiraWebhook, *err
return nebWH, nil return nebWH, nil
} }
func checkProjectsArePublic(jrealm *realms.JIRARealm, projects []string, userID string) *errors.HTTPError {
func checkProjectsArePublic(jrealm *jira.Realm, projects []string, userID string) *errors.HTTPError {
publicCli, err := jrealm.JIRAClient("", true) publicCli, err := jrealm.JIRAClient("", true)
if err != nil { if err != nil {
return &errors.HTTPError{err, "Cannot create public JIRA client", 500} return &errors.HTTPError{err, "Cannot create public JIRA client", 500}

Loading…
Cancel
Save