diff --git a/controllers/token.go b/controllers/token.go
index 427f2e90..5f13db13 100644
--- a/controllers/token.go
+++ b/controllers/token.go
@@ -171,12 +171,16 @@ func (c *ApiController) GetOAuthToken() {
clientSecret := c.Input().Get("client_secret")
code := c.Input().Get("code")
verifier := c.Input().Get("code_verifier")
+ scope := c.Input().Get("scope")
+ username := c.Input().Get("username")
+ password := c.Input().Get("password")
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
+ host := c.Ctx.Request.Host
- c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier)
+ c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
c.ServeJSON()
}
diff --git a/object/application.go b/object/application.go
index c80c08df..61e4548a 100644
--- a/object/application.go
+++ b/object/application.go
@@ -38,6 +38,7 @@ type Application struct {
EnableCodeSignin bool `json:"enableCodeSignin"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
+ GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
diff --git a/object/token.go b/object/token.go
index f6b581c3..05d248ca 100644
--- a/object/token.go
+++ b/object/token.go
@@ -17,6 +17,7 @@ package object
import (
"crypto/sha256"
"encoding/base64"
+ "errors"
"fmt"
"strings"
"time"
@@ -261,7 +262,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}
}
-func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string) *TokenWrapper {
+func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string) *TokenWrapper {
application := GetApplicationByClientId(clientId)
if application == nil {
return &TokenWrapper{
@@ -272,75 +273,30 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
}
}
- if grantType != "authorization_code" {
+ //Check if grantType is allowed in the current application
+ if !isGrantTypeValid(grantType, application.GrantTypes) {
return &TokenWrapper{
- AccessToken: "error: grant_type should be \"authorization_code\"",
+ AccessToken: fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType),
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
- if code == "" {
- return &TokenWrapper{
- AccessToken: "error: authorization code should not be empty",
- TokenType: "",
- ExpiresIn: 0,
- Scope: "",
- }
+ var token *Token
+ var err error
+ switch grantType {
+ case "authorization_code": // Authorization Code Grant
+ token, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
+ case "password": // Resource Owner Password Credentials Grant
+ token, err = GetPasswordToken(application, username, password, scope, host)
+ case "client_credentials": // Client Credentials Grant
+ token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
}
- token := getTokenByCode(code)
- if token == nil {
+ if err != nil {
return &TokenWrapper{
- AccessToken: "error: invalid authorization code",
- TokenType: "",
- ExpiresIn: 0,
- Scope: "",
- }
- }
-
- if application.Name != token.Application {
- return &TokenWrapper{
- AccessToken: "error: the token is for wrong application (client_id)",
- TokenType: "",
- ExpiresIn: 0,
- Scope: "",
- }
- }
-
- if application.ClientSecret != clientSecret {
- return &TokenWrapper{
- AccessToken: "error: invalid client_secret",
- TokenType: "",
- ExpiresIn: 0,
- Scope: "",
- }
- }
-
- if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
- return &TokenWrapper{
- AccessToken: "error: incorrect code_verifier",
- TokenType: "",
- ExpiresIn: 0,
- Scope: "",
- }
- }
-
- if token.CodeIsUsed {
- // anti replay attacks
- return &TokenWrapper{
- AccessToken: "error: authorization code has been used",
- TokenType: "",
- ExpiresIn: 0,
- Scope: "",
- }
- }
-
- if time.Now().Unix() > token.CodeExpireIn {
- // code must be used within 5 minutes
- return &TokenWrapper{
- AccessToken: "error: authorization code has expired",
+ AccessToken: err.Error(),
TokenType: "",
ExpiresIn: 0,
Scope: "",
@@ -459,3 +415,115 @@ func pkceChallenge(verifier string) string {
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
return challenge
}
+
+// Check if grantType is allowed in the current application
+// authorization_code is allowed by default
+func isGrantTypeValid(method string, grantTypes []string) bool {
+ if method == "authorization_code" {
+ return true
+ }
+ for _, m := range grantTypes {
+ if m == method {
+ return true
+ }
+ }
+ return false
+}
+
+// Authorization code flow
+func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, error) {
+ if code == "" {
+ return nil, errors.New("error: authorization code should not be empty")
+ }
+
+ token := getTokenByCode(code)
+ if token == nil {
+ return nil, errors.New("error: invalid authorization code")
+ }
+ if token.CodeIsUsed {
+ // anti replay attacks
+ return nil, errors.New("error: authorization code has been used")
+ }
+ if application.ClientSecret != clientSecret {
+ return nil, errors.New("error: invalid client_secret")
+ }
+
+ if application.Name != token.Application {
+ return nil, errors.New("error: the token is for wrong application (client_id)")
+ }
+
+ if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
+ return nil, errors.New("error: incorrect code_verifier")
+ }
+
+ if time.Now().Unix() > token.CodeExpireIn {
+ // code must be used within 5 minutes
+ return nil, errors.New("error: authorization code has expired")
+ }
+ return token, nil
+}
+
+// Resource Owner Password Credentials flow
+func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, error) {
+ user := getUser(application.Organization, username)
+ if user == nil {
+ return nil, errors.New("error: the user does not exist")
+ }
+ if user.Password != password {
+ return nil, errors.New("error: invalid username or password")
+ }
+ if user.IsForbidden {
+ return nil, errors.New("error: the user is forbidden to sign in, please contact the administrator")
+ }
+ accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
+ if err != nil {
+ return nil, err
+ }
+ token := &Token{
+ Owner: application.Owner,
+ Name: util.GenerateId(),
+ CreatedTime: util.GetCurrentTime(),
+ Application: application.Name,
+ Organization: user.Owner,
+ User: user.Name,
+ Code: util.GenerateClientId(),
+ AccessToken: accessToken,
+ RefreshToken: refreshToken,
+ ExpiresIn: application.ExpireInHours * 60,
+ Scope: scope,
+ TokenType: "Bearer",
+ CodeIsUsed: true,
+ }
+ AddToken(token)
+ return token, nil
+}
+
+// Client Credentials flow
+func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, error) {
+ if application.ClientSecret != clientSecret {
+ return nil, errors.New("error: invalid client_secret")
+ }
+ nullUser := &User{
+ Name: fmt.Sprintf("app/%s", application.Name),
+ }
+ accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
+ if err != nil {
+ return nil, err
+ }
+ token := &Token{
+ Owner: application.Owner,
+ Name: util.GenerateId(),
+ CreatedTime: util.GetCurrentTime(),
+ Application: application.Name,
+ Organization: application.Organization,
+ User: nullUser.Name,
+ Code: util.GenerateClientId(),
+ AccessToken: accessToken,
+ ExpiresIn: application.ExpireInHours * 60,
+ Scope: scope,
+ TokenType: "Bearer",
+ CodeIsUsed: true,
+ }
+ AddToken(token)
+ return token, nil
+}
diff --git a/routers/auto_signin_filter.go b/routers/auto_signin_filter.go
index 1e807bb3..65ac9c96 100644
--- a/routers/auto_signin_filter.go
+++ b/routers/auto_signin_filter.go
@@ -62,7 +62,7 @@ func AutoSigninFilter(ctx *context.Context) {
// "/page?username=abc&password=123"
userId = ctx.Input.Query("username")
password := ctx.Input.Query("password")
- if userId != "" && password != "" {
+ if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
owner, name := util.GetOwnerAndNameFromId(userId)
_, msg := object.CheckUserPassword(owner, name, password)
if msg != "" {
diff --git a/web/src/ApplicationEditPage.js b/web/src/ApplicationEditPage.js
index 5936a435..7437ff91 100644
--- a/web/src/ApplicationEditPage.js
+++ b/web/src/ApplicationEditPage.js
@@ -61,6 +61,9 @@ class ApplicationEditPage extends React.Component {
getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => {
+ if (application.grantTypes === null || application.grantTypes.length === 0) {
+ application.grantTypes = ["authorization_code"];
+ }
this.setState({
application: application,
});
@@ -435,6 +438,27 @@ class ApplicationEditPage extends React.Component {
+
+
+ {Setting.getLabel(i18next.t("application:Grant Types"), i18next.t("application:Grant Types - Tooltip"))} :
+
+
+
+
+
+
{Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :