PLT-3984 Add the ability to regenerate OAuth Client Secret (#3899)
Этот коммит содержится в:
52
api/oauth.go
52
api/oauth.go
@@ -32,7 +32,8 @@ func InitOAuth() {
|
|||||||
BaseRoutes.OAuth.Handle("/allow", ApiUserRequired(allowOAuth)).Methods("GET")
|
BaseRoutes.OAuth.Handle("/allow", ApiUserRequired(allowOAuth)).Methods("GET")
|
||||||
BaseRoutes.OAuth.Handle("/authorized", ApiUserRequired(getAuthorizedApps)).Methods("GET")
|
BaseRoutes.OAuth.Handle("/authorized", ApiUserRequired(getAuthorizedApps)).Methods("GET")
|
||||||
BaseRoutes.OAuth.Handle("/delete", ApiUserRequired(deleteOAuthApp)).Methods("POST")
|
BaseRoutes.OAuth.Handle("/delete", ApiUserRequired(deleteOAuthApp)).Methods("POST")
|
||||||
BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/deauthorize", AppHandlerIndependent(deauthorizeOAuthApp)).Methods("POST")
|
BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/deauthorize", ApiUserRequired(deauthorizeOAuthApp)).Methods("POST")
|
||||||
|
BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/regen_secret", ApiUserRequired(regenerateOAuthSecret)).Methods("POST")
|
||||||
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
|
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
|
||||||
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/login", AppHandlerIndependent(loginWithOAuth)).Methods("GET")
|
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/login", AppHandlerIndependent(loginWithOAuth)).Methods("GET")
|
||||||
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/signup", AppHandlerIndependent(signupWithOAuth)).Methods("GET")
|
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/signup", AppHandlerIndependent(signupWithOAuth)).Methods("GET")
|
||||||
@@ -957,6 +958,55 @@ func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
ReturnStatusOK(w)
|
ReturnStatusOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func regenerateOAuthSecret(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
|
||||||
|
c.Err = model.NewLocAppError("registerOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "")
|
||||||
|
c.Err.StatusCode = http.StatusNotImplemented
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSystemAdmin := c.IsSystemAdmin()
|
||||||
|
|
||||||
|
if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations {
|
||||||
|
if !isSystemAdmin {
|
||||||
|
c.Err = model.NewLocAppError("registerOAuthApp", "api.command.admin_only.app_error", nil, "")
|
||||||
|
c.Err.StatusCode = http.StatusForbidden
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := mux.Vars(r)
|
||||||
|
id := params["id"]
|
||||||
|
|
||||||
|
if len(id) == 0 {
|
||||||
|
c.SetInvalidParam("regenerateOAuthSecret", "id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var app *model.OAuthApp
|
||||||
|
if result := <-Srv.Store.OAuth().GetApp(id); result.Err != nil {
|
||||||
|
c.Err = model.NewLocAppError("regenerateOAuthSecret", "api.oauth.allow_oauth.database.app_error", nil, "")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
app = result.Data.(*model.OAuthApp)
|
||||||
|
|
||||||
|
//validate that is a System Admin or the same user that registered the app
|
||||||
|
if !isSystemAdmin && app.CreatorId != c.Session.UserId {
|
||||||
|
c.Err = model.NewLocAppError("regenerateOAuthSecret", "api.oauth.regenerate_secret.app_error", nil, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.ClientSecret = model.NewId()
|
||||||
|
if update := <-Srv.Store.OAuth().UpdateApp(app); update.Err != nil {
|
||||||
|
c.Err = update.Err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(app.ToJson()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newSession(appName string, user *model.User) (*model.Session, *model.AppError) {
|
func newSession(appName string, user *model.User) (*model.Session, *model.AppError) {
|
||||||
// set new token an session
|
// set new token an session
|
||||||
session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true}
|
session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true}
|
||||||
|
|||||||
@@ -278,6 +278,30 @@ func TestDeauthorizeApp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegenerateOAuthAppSecret(t *testing.T) {
|
||||||
|
th := Setup().InitSystemAdmin()
|
||||||
|
AdminClient := th.SystemAdminClient
|
||||||
|
|
||||||
|
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
|
||||||
|
|
||||||
|
app := &model.OAuthApp{Name: "TestApp6" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
|
||||||
|
|
||||||
|
app = AdminClient.Must(AdminClient.RegisterApp(app)).Data.(*model.OAuthApp)
|
||||||
|
|
||||||
|
if regenApp, err := AdminClient.RegenerateOAuthAppSecret(app.Id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
app2 := regenApp.Data.(*model.OAuthApp)
|
||||||
|
if app2.Id != app.Id {
|
||||||
|
t.Fatal("Should have been the same app Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if app2.ClientSecret == app.ClientSecret {
|
||||||
|
t.Fatal("Should have been diferent client Secrets")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOAuthDeleteApp(t *testing.T) {
|
func TestOAuthDeleteApp(t *testing.T) {
|
||||||
th := Setup().InitBasic().InitSystemAdmin()
|
th := Setup().InitBasic().InitSystemAdmin()
|
||||||
Client := th.BasicClient
|
Client := th.BasicClient
|
||||||
|
|||||||
@@ -1065,6 +1065,10 @@
|
|||||||
"id": "api.oauth.init.debug",
|
"id": "api.oauth.init.debug",
|
||||||
"translation": "Initializing oauth api routes"
|
"translation": "Initializing oauth api routes"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.oauth.regenerate_secret.app_error",
|
||||||
|
"translation": "Inappropriate permissions to regenerate the OAuth2 App Secret"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.oauth.register_oauth_app.turn_off.app_error",
|
"id": "api.oauth.register_oauth_app.turn_off.app_error",
|
||||||
"translation": "The system admin has turned off OAuth2 Service Provider."
|
"translation": "The system admin has turned off OAuth2 Service Provider."
|
||||||
|
|||||||
@@ -1557,6 +1557,19 @@ func (c *Client) OAuthDeauthorizeApp(clientId string) *AppError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegenerateOAuthAppSecret generates a new OAuth App Client Secret. On success
|
||||||
|
// it returns an OAuth2 App. Must be authenticated as a user and the same user who
|
||||||
|
// registered the app or a System Admin.
|
||||||
|
func (c *Client) RegenerateOAuthAppSecret(clientId string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiPost("/oauth/"+clientId+"/regen_secret", ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) {
|
func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) {
|
||||||
if r, err := c.DoPost("/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil {
|
if r, err := c.DoPost("/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ func (as SqlOAuthStore) UpdateApp(app *model.OAuthApp) StoreChannel {
|
|||||||
} else {
|
} else {
|
||||||
oldApp := oldAppResult.(*model.OAuthApp)
|
oldApp := oldAppResult.(*model.OAuthApp)
|
||||||
app.CreateAt = oldApp.CreateAt
|
app.CreateAt = oldApp.CreateAt
|
||||||
app.ClientSecret = oldApp.ClientSecret
|
|
||||||
app.CreatorId = oldApp.CreatorId
|
app.CreatorId = oldApp.CreatorId
|
||||||
|
|
||||||
if count, err := as.GetMaster().Update(app); err != nil {
|
if count, err := as.GetMaster().Update(app); err != nil {
|
||||||
|
|||||||
@@ -69,9 +69,6 @@ func TestOAuthStoreUpdateApp(t *testing.T) {
|
|||||||
if ua1.CreateAt == 1 {
|
if ua1.CreateAt == 1 {
|
||||||
t.Fatal("create at should not have updated")
|
t.Fatal("create at should not have updated")
|
||||||
}
|
}
|
||||||
if ua1.ClientSecret == "pwd" {
|
|
||||||
t.Fatal("client secret should not have updated")
|
|
||||||
}
|
|
||||||
if ua1.CreatorId == "12345678901234567890123456" {
|
if ua1.CreatorId == "12345678901234567890123456" {
|
||||||
t.Fatal("creator id should not have updated")
|
t.Fatal("creator id should not have updated")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1573,6 +1573,16 @@ export default class Client {
|
|||||||
end(this.handleResponse.bind(this, 'deauthorizeOAuthApp', success, error));
|
end(this.handleResponse.bind(this, 'deauthorizeOAuthApp', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regenerateOAuthAppSecret(id, success, error) {
|
||||||
|
request.
|
||||||
|
post(`${this.getOAuthRoute()}/${id}/regen_secret`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
send().
|
||||||
|
end(this.handleResponse.bind(this, 'regenerateOAuthAppSecret', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
// Routes for Hooks
|
// Routes for Hooks
|
||||||
|
|
||||||
addIncomingHook(hook, success, error) {
|
addIncomingHook(hook, success, error) {
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import FormError from 'components/form_error.jsx';
|
||||||
|
|
||||||
|
import Client from 'client/web_client.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||||
@@ -23,6 +26,7 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
|
|
||||||
this.handleShowClientSecret = this.handleShowClientSecret.bind(this);
|
this.handleShowClientSecret = this.handleShowClientSecret.bind(this);
|
||||||
this.handleHideClientScret = this.handleHideClientScret.bind(this);
|
this.handleHideClientScret = this.handleHideClientScret.bind(this);
|
||||||
|
this.handleRegenerate = this.handleRegenerate.bind(this);
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
this.handleDelete = this.handleDelete.bind(this);
|
||||||
|
|
||||||
this.matchesFilter = this.matchesFilter.bind(this);
|
this.matchesFilter = this.matchesFilter.bind(this);
|
||||||
@@ -42,6 +46,21 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
this.setState({clientSecret: FAKE_SECRET});
|
this.setState({clientSecret: FAKE_SECRET});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRegenerate(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
Client.regenerateOAuthAppSecret(
|
||||||
|
this.props.oauthApp.id,
|
||||||
|
(data) => {
|
||||||
|
this.props.oauthApp.client_secret = data.client_secret;
|
||||||
|
this.handleShowClientSecret(e);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.setState({error: err.message});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleDelete(e) {
|
handleDelete(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -58,6 +77,15 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const oauthApp = this.props.oauthApp;
|
const oauthApp = this.props.oauthApp;
|
||||||
|
let error;
|
||||||
|
|
||||||
|
if (this.state.error) {
|
||||||
|
error = (
|
||||||
|
<FormError
|
||||||
|
error={this.state.error}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.matchesFilter(oauthApp, this.props.filter)) {
|
if (!this.matchesFilter(oauthApp, this.props.filter)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -107,9 +135,9 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
isTrusted = Utils.localizeMessage('installed_oauth_apps.trusted.no', 'No');
|
isTrusted = Utils.localizeMessage('installed_oauth_apps.trusted.no', 'No');
|
||||||
}
|
}
|
||||||
|
|
||||||
let action;
|
let showHide;
|
||||||
if (this.state.clientSecret === FAKE_SECRET) {
|
if (this.state.clientSecret === FAKE_SECRET) {
|
||||||
action = (
|
showHide = (
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
onClick={this.handleShowClientSecret}
|
onClick={this.handleShowClientSecret}
|
||||||
@@ -121,7 +149,7 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
action = (
|
showHide = (
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
onClick={this.handleHideClientScret}
|
onClick={this.handleHideClientScret}
|
||||||
@@ -134,6 +162,18 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const regen = (
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
onClick={this.handleRegenerate}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.regenSecret'
|
||||||
|
defaultMessage='Regenerate Secret'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
if (oauthApp.icon_url) {
|
if (oauthApp.icon_url) {
|
||||||
icon = (
|
icon = (
|
||||||
@@ -152,6 +192,7 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{error}
|
||||||
{description}
|
{description}
|
||||||
<div className='item-details__row'>
|
<div className='item-details__row'>
|
||||||
<span className='item-details__url'>
|
<span className='item-details__url'>
|
||||||
@@ -201,7 +242,9 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='item-actions'>
|
<div className='item-actions'>
|
||||||
{action}
|
{showHide}
|
||||||
|
{' - '}
|
||||||
|
{regen}
|
||||||
{' - '}
|
{' - '}
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
|
|||||||
@@ -1308,6 +1308,7 @@
|
|||||||
"installed_integrations.creation": "Created by {creator} on {createAt, date, full}",
|
"installed_integrations.creation": "Created by {creator} on {createAt, date, full}",
|
||||||
"installed_integrations.delete": "Delete",
|
"installed_integrations.delete": "Delete",
|
||||||
"installed_integrations.hideSecret": "Hide Secret",
|
"installed_integrations.hideSecret": "Hide Secret",
|
||||||
|
"installed_integrations.regenSecret": "Regenerate Secret",
|
||||||
"installed_integrations.regenToken": "Regenerate Token",
|
"installed_integrations.regenToken": "Regenerate Token",
|
||||||
"installed_integrations.showSecret": "Show Secret",
|
"installed_integrations.showSecret": "Show Secret",
|
||||||
"installed_integrations.token": "Token: {token}",
|
"installed_integrations.token": "Token: {token}",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user