Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Oauth setup and connect disconnect command #126

Open
wants to merge 6 commits into
base: flow_setup
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions assets/templates/api/v1/oauth2/complete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
color: rgb(23, 43, 77);
letter-spacing: -0.01em;
}

.flex-parent {
padding: 50px;
}

.btn {
-webkit-transition: all 0.15s ease;
-moz-transition: all 0.15s ease;
-o-transition: all 0.15s ease;
transition: all 0.15s ease false;
padding: 0.5em 1em;
font-size: inherit;
border: none;
height: auto;
border-radius: 4px;
cursor: pointer;
}

.btn-primary {
color: rgb(255, 255, 255);
background: rgb(0, 82, 204);
}

.btn-primary:hover,
.btn-primary:active {
background: rgb(0, 101, 255);
}

.accounts-container {
padding: 1.6em 0 0.8em;
opacity: 0.6;
}

.icon--check {
margin-right: 4px;
}

.message-container {
margin-top: 20px;
}
</style>
<script>
function closeTab() {
window.close();
}
</script>
</head>
<body>
<div class="flex-parent">
<h3>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="15" class="icon--check" viewBox="0 0 18 14">
<path fill="#0052CC" d="M100.649576,76.1740942 C100.649576,76.4531124 100.537969,76.7321306 100.337075,76.9330237 L90.7388497,86.5312494 C90.5379566,86.7321425 90.2589384,86.8437498 89.9799202,86.8437498 C89.700902,86.8437498 89.4218838,86.7321425 89.2209908,86.5312494 L83.6629484,80.9732071 C83.4620553,80.772314 83.350448,80.4932958 83.350448,80.2142776 C83.350448,79.9352594 83.4620553,79.6562412 83.6629484,79.4553481 L85.1808074,77.9374892 C85.3817005,77.7365961 85.6607186,77.6249888 85.9397368,77.6249888 C86.218755,77.6249888 86.4977732,77.7365961 86.6986663,77.9374892 L89.9799202,81.2299038 L97.3013575,73.8973058 C97.5022506,73.6964127 97.7812688,73.5848054 98.060287,73.5848054 C98.3393052,73.5848054 98.6183234,73.6964127 98.8192165,73.8973058 L100.337075,75.4151648 C100.537969,75.6160579 100.649576,75.895076 100.649576,76.1740942 Z" transform="translate(-83 -73)"/>
</svg>
Mattermost user is now connected to Confluence
</h3>
<div class="accounts-container">
<div>Mattermost account: {{ .MattermostDisplayName }}</div>
<div>Confluence account: {{ .ConfluenceDisplayName }}</div>
</div>
<div class="message-container">
<p>You may now safely close this tab.</p>
<button class="btn btn-primary" onclick="closeTab()">Close Tab</button>
</div>
</div>
</body>
</html>
31 changes: 31 additions & 0 deletions assets/templates/other/message.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
color: rgb(23, 43, 77);
letter-spacing: -0.01em;
}

.flex-parent {
padding: 50px;
}

.message-container {
padding: 1.6em 0 0.8em;
opacity: .6;
}
</style>
<link rel="stylesheet" href="https://unpkg.com/@atlaskit/[email protected]/dist/bundle.css" media="all">
</head>
<body>
<div class="flex-parent">
<h3>
{{ .Header}}
</h3>
<div class="message-container">
<div>{{ .Message }}</div>
</div>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
go.uber.org/atomic v1.11.0
golang.org/x/oauth2 v0.21.0
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
2 changes: 1 addition & 1 deletion server/atlassian_connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var atlassianConnectJSON = &Endpoint{
RequiresAdmin: false,
}

func renderAtlassianConnectJSON(w http.ResponseWriter, r *http.Request) {
func renderAtlassianConnectJSON(w http.ResponseWriter, r *http.Request, _ *Plugin) {
conf := config.GetConfig()
if status, err := verifyHTTPSecret(conf.Secret, r.FormValue("secret")); err != nil {
http.Error(w, err.Error(), status)
Expand Down
151 changes: 151 additions & 0 deletions server/auth_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License for license information.

package main

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"io"

"github.com/pkg/errors"
"golang.org/x/oauth2"

"github.com/mattermost/mattermost-plugin-confluence/server/store"
)

type AuthToken struct {
Token *oauth2.Token `json:"token,omitempty"`
}

func (p *Plugin) NewEncodedAuthToken(token *oauth2.Token) (encodedToken string, returnErr error) {
defer func() {
if returnErr == nil {
return
}
returnErr = errors.WithMessage(returnErr, "failed to create auth token")
}()

encryptionSecret, err := store.EnsureAuthTokenEncryptionSecret()
if err != nil {
return "", err
}

t := AuthToken{
Token: token,
}

jsonBytes, err := json.Marshal(t)
if err != nil {
return "", err
}

encrypted, err := encrypt(jsonBytes, encryptionSecret)
if err != nil {
return "", err
}

return encode(encrypted), nil
}

func (p *Plugin) ParseAuthToken(encoded string) (token *oauth2.Token, returnErr error) {
defer func() {
if returnErr == nil {
return
}
returnErr = errors.WithMessage(returnErr, "failed to parse auth token")
}()

t := AuthToken{}
encryptionSecret, err := store.EnsureAuthTokenEncryptionSecret()
if err != nil {
return nil, err
}

decoded, err := decode(encoded)
if err != nil {
return nil, err
}

jsonBytes, err := decrypt(decoded, encryptionSecret)
if err != nil {
return nil, err
}

if err = json.Unmarshal(jsonBytes, &t); err != nil {
return nil, err
}

return t.Token, nil
}

func encode(encrypted []byte) string {
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(encrypted)))
base64.URLEncoding.Encode(encoded, encrypted)
return string(encoded)
}

func encrypt(plain, secret []byte) ([]byte, error) {
if len(secret) == 0 {
return plain, nil
}

block, err := aes.NewCipher(secret)
if err != nil {
return nil, err
}

aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonce := make([]byte, aesgcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}

sealed := aesgcm.Seal(nil, nonce, plain, nil)
return append(nonce, sealed...), nil
}

func decode(encoded string) ([]byte, error) {
decoded := make([]byte, base64.URLEncoding.DecodedLen(len(encoded)))
n, err := base64.URLEncoding.Decode(decoded, []byte(encoded))
if err != nil {
return nil, err
}
return decoded[:n], nil
}

func decrypt(encrypted, secret []byte) ([]byte, error) {
if len(secret) == 0 {
return encrypted, nil
}

block, err := aes.NewCipher(secret)
if err != nil {
return nil, err
}

aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonceSize := aesgcm.NonceSize()
if len(encrypted) < nonceSize {
return nil, errors.New("token too short")
}

nonce, encrypted := encrypted[:nonceSize], encrypted[nonceSize:]
plain, err := aesgcm.Open(nil, nonce, encrypted, nil)
if err != nil {
return nil, err
}

return plain, nil
}
15 changes: 15 additions & 0 deletions server/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import "github.com/mattermost/mattermost-plugin-confluence/server/util/types"

// Client is the combined interface for all upstream APIs and convenience methods.
type Client interface {
RESTService
}

// RESTService is the low-level interface for invoking the upstream service.
// Endpoint can be a "short" API URL path, including the version desired, like "v3/user",
// or a fully-qualified URL, with a non-empty scheme.
type RESTService interface {
GetSelf() (*types.ConfluenceUser, error)
}
54 changes: 54 additions & 0 deletions server/client_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"net/http"

"github.com/pkg/errors"

"github.com/mattermost/mattermost-plugin-confluence/server/service"
"github.com/mattermost/mattermost-plugin-confluence/server/util/types"
)

const (
PathCurrentUser = "/rest/api/user/current"
PathAdminData = "/rest/api/audit"
)

type confluenceServerClient struct {
URL string
HTTPClient *http.Client
}

type ConfluenceServerUser struct {
UserKey string `json:"userKey"`
Username string `json:"username"`
DisplayName string `json:"displayName"`
Type string `json:"type"`
}

type AdminData struct {
Number int `json:"number"`
Units string `json:"units"`
}

func newServerClient(url string, httpClient *http.Client) Client {
return &confluenceServerClient{
URL: url,
HTTPClient: httpClient,
}
}

func (csc *confluenceServerClient) GetSelf() (*types.ConfluenceUser, error) {
confluenceServerUser := &ConfluenceServerUser{}
if _, err := service.CallJSONWithURL(csc.URL, PathCurrentUser, http.MethodGet, nil, confluenceServerUser, csc.HTTPClient); err != nil {
return nil, errors.Wrap(err, "confluence GetSelf. Error getting the current user")
}

confluenceUser := &types.ConfluenceUser{
AccountID: confluenceServerUser.UserKey,
Name: confluenceServerUser.Username,
DisplayName: confluenceServerUser.DisplayName,
}

return confluenceUser, nil
}
Loading