Skip to content

Commit

Permalink
Initial commit to support OAuth2 authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
srkgupta committed Mar 23, 2023
1 parent 1e10204 commit c24cb05
Show file tree
Hide file tree
Showing 10 changed files with 573 additions and 40 deletions.
11 changes: 11 additions & 0 deletions assets/templates/command/install_cloud_oauth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
1. Navigate to the [Atlassian Developer Console Page](https://developer.atlassian.com/console/myapps).
2. From the **Create** dropdown, select the option **OAuth 2.0 Integration** to create a new OAuth 2.0 App
3. Name your app according to its purpose, accept the terms and click on **Create** button.
4. Select **Authorization** in the left menu.
5. Next to OAuth 2.0 (3LO), click on the **Add** button to Configure.
6. Enter the Callback URL as `{{ .PluginURL }}/oauth/complete`
7. Click **Save Changes**.
8. Select Permissions in the left menu.
9. Next to the Jira API, select Add.

If you see an option to create a Jira issue, you're all set! If not, refer to our [documentation](https://mattermost.gitbook.io/plugin-jira) for troubleshooting help.
76 changes: 76 additions & 0 deletions assets/templates/oauth2/complete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!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;
-webkit-transition-delay: 0s;
-moz-transition: all 0.15s ease;
-o-transition: all 0.15s ease;
transition: all 0.15s ease false;
padding-right: 1em;
padding-left: 1em;
font-size: inherit;
border: none;
height: 2.4em;
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);
}

.btn-link {
color: #505f79;
background: #f4f5f7;
}

.btn-link:hover,
.btn-link:active {
background: #ebecf0;
}

.accounts-container {
padding: 1.6em 0 0.8em;
opacity: .6;
}
.icon--check {
margin-right: 4px;
}
</style>
<link rel="stylesheet" href="https://unpkg.com/@atlaskit/css-reset@2.0.0/dist/bundle.css" media="all">
</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 Jira
</h3>
<div class="accounts-container">
<div>Mattermost account: {{ .MattermostDisplayName }}</div>
<div>Jira account: {{ .JiraDisplayName }}</div>
<div>It is now safe to close this browser window.</div>
</div>
<a href="{{ .RevokeURL }}" class="btn btn-link">Disconnect</a>
</div>
</body>
</html>

100 changes: 66 additions & 34 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,38 @@ const commandTrigger = "jira"

var jiraCommandHandler = CommandHandler{
handlers: map[string]CommandHandlerFunc{
"assign": executeAssign,
"connect": executeConnect,
"disconnect": executeDisconnect,
"help": executeHelp,
"info": executeInfo,
"install/cloud": executeInstanceInstallCloud,
"install/server": executeInstanceInstallServer,
"instance/alias": executeInstanceAlias,
"instance/unalias": executeInstanceUnalias,
"instance/connect": executeConnect,
"instance/disconnect": executeDisconnect,
"instance/install/cloud": executeInstanceInstallCloud,
"instance/install/server": executeInstanceInstallServer,
"instance/list": executeInstanceList,
"instance/settings": executeSettings,
"instance/uninstall": executeInstanceUninstall,
"instance/v2": executeInstanceV2Legacy,
"issue/assign": executeAssign,
"issue/transition": executeTransition,
"issue/unassign": executeUnassign,
"issue/view": executeView,
"settings": executeSettings,
"subscribe/list": executeSubscribeList,
"transition": executeTransition,
"unassign": executeUnassign,
"uninstall": executeInstanceUninstall,
"view": executeView,
"v2revert": executeV2Revert,
"webhook": executeWebhookURL,
"setup": executeSetup,
"assign": executeAssign,
"connect": executeConnect,
"disconnect": executeDisconnect,
"help": executeHelp,
"info": executeInfo,
"install/cloud": executeInstanceInstallCloud,
"install/cloud-oauth": executeInstanceInstallCloudOAuth,
"install/server": executeInstanceInstallServer,
"instance/alias": executeInstanceAlias,
"instance/unalias": executeInstanceUnalias,
"instance/connect": executeConnect,
"instance/disconnect": executeDisconnect,
"instance/install/cloud": executeInstanceInstallCloud,
"instance/install/cloud-oauth": executeInstanceInstallCloudOAuth,
"instance/install/server": executeInstanceInstallServer,
"instance/list": executeInstanceList,
"instance/settings": executeSettings,
"instance/uninstall": executeInstanceUninstall,
"instance/v2": executeInstanceV2Legacy,
"issue/assign": executeAssign,
"issue/transition": executeTransition,
"issue/unassign": executeUnassign,
"issue/view": executeView,
"settings": executeSettings,
"subscribe/list": executeSubscribeList,
"transition": executeTransition,
"unassign": executeUnassign,
"uninstall": executeInstanceUninstall,
"view": executeView,
"v2revert": executeV2Revert,
"webhook": executeWebhookURL,
"setup": executeSetup,
},
defaultHandler: executeJiraDefault,
}
Expand All @@ -77,9 +79,11 @@ const commonHelpText = "\n" +
const sysAdminHelpText = "\n###### For System Administrators:\n" +
"Install Jira instances:\n" +
"* `/jira instance install cloud [jiraURL]` - Connect Mattermost to a Jira Cloud instance located at <jiraURL>\n" +
"* `/jira instance install cloud-oauth [jiraURL]` - Connect Mattermost to a Jira Cloud instance using OAuth 2.0 located at <jiraURL>\n" +
"* `/jira instance install server [jiraURL]` - Connect Mattermost to a Jira Server or Data Center instance located at <jiraURL>\n" +
"Uninstall Jira instances:\n" +
"* `/jira instance uninstall cloud [jiraURL]` - Disconnect Mattermost from a Jira Cloud instance located at <jiraURL>\n" +
"* `/jira instance uninstall cloud-oauth [jiraURL]` - Disconnect Mattermost from a Jira Cloud instance using OAuth 2.0 located at <jiraURL>\n" +
"* `/jira instance uninstall server [jiraURL]` - Disconnect Mattermost from a Jira Server or Data Center instance located at <jiraURL>\n" +
"Manage channel subscriptions:\n" +
"* `/jira subscribe ` - Configure the Jira notifications sent to this channel\n" +
Expand Down Expand Up @@ -167,17 +171,18 @@ func createInstanceCommand(optInstance bool) *model.AutocompleteData {
jiraTypes := []model.AutocompleteListItem{
{HelpText: "Jira Server or Datacenter", Item: "server"},
{HelpText: "Jira Cloud (atlassian.net)", Item: "cloud"},
{HelpText: "Jira Cloud OAuth 2.0 (atlassian.net)", Item: "cloud-oauth"},
}

install := model.NewAutocompleteData(
"install", "[cloud|server] [URL]", "Connect Mattermost to a Jira instance")
install.AddStaticListArgument("Jira type: server or cloud", true, jiraTypes)
"install", "[cloud|server|cloud-oauth] [URL]", "Connect Mattermost to a Jira instance")
install.AddStaticListArgument("Jira type: server, cloud or cloud-oauth", true, jiraTypes)
install.AddTextArgument("Jira URL", "Enter the Jira URL, e.g. https://mattermost.atlassian.net", "")
install.RoleID = model.SystemAdminRoleId

uninstall := model.NewAutocompleteData(
"uninstall", "[cloud|server] [URL]", "Disconnect Mattermost from a Jira instance")
uninstall.AddStaticListArgument("Jira type: server or cloud", true, jiraTypes)
"uninstall", "[cloud|server|cloud-oauth] [URL]", "Disconnect Mattermost from a Jira instance")
uninstall.AddStaticListArgument("Jira type: server, cloud or cloud-oauth", true, jiraTypes)
uninstall.AddDynamicListArgument("Jira instance", routeAutocompleteInstalledInstance, true)
uninstall.RoleID = model.SystemAdminRoleId

Expand Down Expand Up @@ -796,6 +801,33 @@ func executeInstanceInstallCloud(p *Plugin, c *plugin.Context, header *model.Com
})
}

func executeInstanceInstallCloudOAuth(p *Plugin, c *plugin.Context, header *model.CommandArgs, args ...string) *model.CommandResponse {
authorized, err := authorizedSysAdmin(p, header.UserId)
if err != nil {
return p.responsef(header, "%v", err)
}
if !authorized {
return p.responsef(header, "`/jira install` can only be run by a system administrator.")
}
if len(args) != 3 {
return p.help(header)
}

clientID := args[1]
clientSecret := args[2]

jiraURL, instance, err := p.installCloudOAuthInstance(args[0], clientID, clientSecret)
if err != nil {
return p.responsef(header, err.Error())
}

return p.respondCommandTemplate(header, "/command/install_cloud_oath.md", map[string]string{
"JiraURL": jiraURL,
"PluginURL": p.GetPluginURL(),
"MattermostKey": instance.GetMattermostKey(),
})
}

func executeInstanceInstallServer(p *Plugin, c *plugin.Context, header *model.CommandArgs, args ...string) *model.CommandResponse {
authorized, err := authorizedSysAdmin(p, header.UserId)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const (
routeUserConnect = "/user/connect"
routeUserDisconnect = "/user/disconnect"
routeSharePublicly = "/api/v2/share-issue-publicly"
routeOAuth2Complete = "/oauth2/complete.html"
)

const routePrefixInstance = "instance"
Expand Down Expand Up @@ -102,6 +103,7 @@ func (p *Plugin) serveHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
routeUserDisconnect,
routeUserConnect,
routeUserStart,
routeOAuth2Complete,
routeAPISubscribeWebhook:

if path == routeIncomingWebhook && instanceURL == "" {
Expand Down Expand Up @@ -177,6 +179,10 @@ func (p *Plugin) serveHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
case routeUserDisconnect:
return p.httpOAuth1aDisconnect(w, r, callbackInstanceID)

// OAuth2 (Jira Cloud)
case routeOAuth2Complete:
return p.httpOAuth2Complete(w, r, callbackInstanceID)

// User connect/disconnect links
case routeUserConnect:
return p.httpUserConnect(w, r, callbackInstanceID)
Expand Down
5 changes: 3 additions & 2 deletions server/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (
type InstanceType string

const (
CloudInstanceType = InstanceType("cloud")
ServerInstanceType = InstanceType("server")
CloudInstanceType = InstanceType("cloud")
ServerInstanceType = InstanceType("server")
CloudOAuthInstanceType = InstanceType("cloud-oauth")
)

type Instance interface {
Expand Down
Loading

0 comments on commit c24cb05

Please sign in to comment.