Compare commits

...

9 Commits

14 changed files with 95 additions and 49 deletions

View File

@ -139,6 +139,10 @@ func (c *ApiController) GetUserApplication() {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The organization: %s should have one application at least"), user.Owner))
return
}
c.ResponseOk(object.GetMaskedApplication(application, userId))
}

View File

@ -271,6 +271,14 @@ func (c *ApiController) RefreshToken() {
c.ServeJSON()
}
func (c *ApiController) ResponseTokenError(errorMsg string) {
c.Data["json"] = &object.TokenError{
Error: errorMsg,
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
// IntrospectToken
// @Title IntrospectToken
// @Tag Login API
@ -293,40 +301,33 @@ func (c *ApiController) IntrospectToken() {
clientId = c.Input().Get("client_id")
clientSecret = c.Input().Get("client_secret")
if clientId == "" || clientSecret == "" {
c.ResponseError(c.T("token:Empty clientId or clientSecret"))
c.Data["json"] = &object.TokenError{
Error: object.InvalidRequest,
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
c.ResponseTokenError(object.InvalidRequest)
return
}
}
application, err := object.GetApplicationByClientId(clientId)
if err != nil {
c.ResponseError(err.Error())
c.ResponseTokenError(err.Error())
return
}
if application == nil || application.ClientSecret != clientSecret {
c.ResponseError(c.T("token:Invalid application or wrong clientSecret"))
c.Data["json"] = &object.TokenError{
Error: object.InvalidClient,
}
c.SetTokenErrorHttpStatus()
return
}
token, err := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
if err != nil {
c.ResponseError(err.Error())
c.ResponseTokenError(c.T("token:Invalid application or wrong clientSecret"))
return
}
token, err := object.GetTokenByTokenValue(tokenValue)
if err != nil {
c.ResponseTokenError(err.Error())
return
}
if token == nil {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
if err != nil || jwtToken.Valid() != nil {
// and token revoked case. but we not implement

View File

@ -93,6 +93,8 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
user.CreatedTime = value
case "UpdatedTime":
user.UpdatedTime = value
case "DeletedTime":
user.DeletedTime = value
case "Id":
user.Id = value
case "Type":
@ -266,6 +268,7 @@ func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]stri
m["Name"] = user.Name
m["CreatedTime"] = user.CreatedTime
m["UpdatedTime"] = user.UpdatedTime
m["DeletedTime"] = user.DeletedTime
m["Id"] = user.Id
m["Type"] = user.Type
m["Password"] = user.Password

View File

@ -186,6 +186,26 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
return &token, nil
}
func GetTokenByTokenValue(tokenValue string) (*Token, error) {
token, err := GetTokenByAccessToken(tokenValue)
if err != nil {
return nil, err
}
if token != nil {
return token, nil
}
token, err = GetTokenByRefreshToken(tokenValue)
if err != nil {
return nil, err
}
if token != nil {
return token, nil
}
return nil, nil
}
func updateUsedByCode(token *Token) bool {
affected, err := ormer.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
if err != nil {
@ -283,20 +303,6 @@ func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, e
return affected != 0, application, token, nil
}
func GetTokenByTokenAndApplication(token string, application string) (*Token, error) {
tokenResult := Token{}
existed, err := ormer.Engine.Where("(refresh_token = ? or access_token = ? ) and application = ?", token, token, application).Get(&tokenResult)
if err != nil {
return nil, err
}
if !existed {
return nil, nil
}
return &tokenResult, nil
}
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string, lang string) (string, *Application, error) {
if responseType != "code" && responseType != "token" && responseType != "id_token" {
return fmt.Sprintf(i18n.Translate(lang, "token:Grant_type: %s is not supported in this application"), responseType), nil, nil

View File

@ -40,7 +40,7 @@ type UserShort struct {
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Avatar string `xorm:"varchar(500)" json:"avatar"`
Email string `xorm:"varchar(100) index" json:"email"`
Phone string `xorm:"varchar(20) index" json:"phone"`
Phone string `xorm:"varchar(100) index" json:"phone"`
}
type UserWithoutThirdIdp struct {
@ -48,6 +48,7 @@ type UserWithoutThirdIdp struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100) index" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
DeletedTime string `xorm:"varchar(100)" json:"deletedTime"`
Id string `xorm:"varchar(100) index" json:"id"`
Type string `xorm:"varchar(100)" json:"type"`
@ -62,7 +63,7 @@ type UserWithoutThirdIdp struct {
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"`
Phone string `xorm:"varchar(20) index" json:"phone"`
Phone string `xorm:"varchar(100) index" json:"phone"`
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
Region string `xorm:"varchar(100)" json:"region"`
Location string `xorm:"varchar(100)" json:"location"`
@ -167,6 +168,7 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
Name: user.Name,
CreatedTime: user.CreatedTime,
UpdatedTime: user.UpdatedTime,
DeletedTime: user.DeletedTime,
Id: user.Id,
Type: user.Type,

View File

@ -49,6 +49,7 @@ type User struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100) index" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
DeletedTime string `xorm:"varchar(100)" json:"deletedTime"`
Id string `xorm:"varchar(100) index" json:"id"`
ExternalId string `xorm:"varchar(100) index" json:"externalId"`
@ -64,7 +65,7 @@ type User struct {
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"`
Phone string `xorm:"varchar(20) index" json:"phone"`
Phone string `xorm:"varchar(100) index" json:"phone"`
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
Region string `xorm:"varchar(100)" json:"region"`
Location string `xorm:"varchar(100)" json:"location"`
@ -639,7 +640,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
if len(columns) == 0 {
columns = []string{
"owner", "display_name", "avatar", "first_name", "last_name",
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret",
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
@ -658,6 +659,10 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
columns = append(columns, "updated_time")
user.UpdatedTime = util.GetCurrentTime()
if len(user.DeletedTime) > 0 {
columns = append(columns, "deleted_time")
}
if util.ContainsString(columns, "groups") {
_, err := userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
if err != nil {

View File

@ -134,6 +134,7 @@ func UploadUsers(owner string, path string) (bool, error) {
LastSigninIp: parseLineItem(&line, 38),
Ldap: "",
Properties: map[string]string{},
DeletedTime: parseLineItem(&line, 39),
}
if _, ok := oldUserMap[user.GetId()]; !ok {

View File

@ -7446,6 +7446,9 @@
"displayName": {
"type": "string"
},
"deletedTime": {
"type": "string"
},
"douyin": {
"type": "string"
},

View File

@ -4900,6 +4900,8 @@ definitions:
type: string
deezer:
type: string
deletedTime:
type: string
digitalocean:
type: string
dingtalk:

View File

@ -1448,7 +1448,7 @@ export function getFriendlyUserName(account) {
}
export function getUserCommonFields() {
return ["Owner", "Name", "CreatedTime", "UpdatedTime", "Id", "Type", "Password", "PasswordSalt", "DisplayName", "FirstName", "LastName", "Avatar", "PermanentAvatar",
return ["Owner", "Name", "CreatedTime", "UpdatedTime", "DeletedTime", "Id", "Type", "Password", "PasswordSalt", "DisplayName", "FirstName", "LastName", "Avatar", "PermanentAvatar",
"Email", "EmailVerified", "Phone", "Location", "Address", "Affiliation", "Title", "IdCardType", "IdCard", "Homepage", "Bio", "Tag", "Region",
"Language", "Gender", "Birthday", "Education", "Score", "Ranking", "IsDefaultAvatar", "IsOnline", "IsAdmin", "IsForbidden", "IsDeleted", "CreatedIp",
"PreferredMfaType", "TotpSecret", "SignupApplication"];

View File

@ -27,6 +27,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
import PasswordModal from "./common/modal/PasswordModal";
import ResetModal from "./common/modal/ResetModal";
import AffiliationSelect from "./common/select/AffiliationSelect";
import moment from "moment";
import OAuthWidget from "./common/OAuthWidget";
import SamlWidget from "./common/SamlWidget";
import RegionSelect from "./common/select/RegionSelect";
@ -122,6 +123,17 @@ class UserEditPage extends React.Component {
this.setState({
applications: res.data || [],
});
const applications = res.data;
if (this.state.user) {
if (this.state.user.signupApplication === "" || applications.filter(application => application.name === this.state.user.signupApplication).length === 0) {
if (applications.length > 0) {
this.updateUserField("signupApplication", applications[0].name);
} else {
this.updateUserField("signupApplication", "");
}
}
}
});
}
@ -858,6 +870,7 @@ class UserEditPage extends React.Component {
<Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isDeleted} onChange={checked => {
this.updateUserField("isDeleted", checked);
this.updateUserField("deletedTime", checked ? moment().format() : "");
}} />
</Col>
</Row>
@ -890,11 +903,9 @@ class UserEditPage extends React.Component {
</Space>
{item.enabled ? (
<Space>
{item.enabled ?
<Tag icon={<CheckCircleOutlined />} color="success">
{i18next.t("general:Enabled")}
</Tag> : null
}
<Tag icon={<CheckCircleOutlined />} color="success">
{i18next.t("general:Enabled")}
</Tag>
{item.isPreferred ?
<Tag icon={<CheckCircleOutlined />} color="blue" style={{marginRight: 20}} >
{i18next.t("mfa:preferred")}
@ -916,18 +927,23 @@ class UserEditPage extends React.Component {
{i18next.t("mfa:Set preferred")}
</Button>
}
{this.isSelf() ? <Button type={"default"} onClick={() => {
this.props.history.push(`/mfa/setup?mfaType=${item.mfaType}`);
}}>
{i18next.t("general:Edit")}
</Button> : null}
</Space>
) :
<Space>
{item.mfaType !== TotpMfaType && Setting.isAdminUser(this.props.account) && window.location.href.indexOf("/users") !== -1 ?
{item.mfaType !== TotpMfaType && Setting.isLocalAdminUser(this.props.account) && !this.isSelf() ?
<EnableMfaModal user={this.state.user} mfaType={item.mfaType} onSuccess={() => {
this.getUser();
}} /> : null}
<Button type={"default"} onClick={() => {
{this.isSelf() ? <Button type={"default"} onClick={() => {
this.props.history.push(`/mfa/setup?mfaType=${item.mfaType}`);
}}>
{i18next.t("mfa:Setup")}
</Button>
</Button> : null}
</Space>}
</List.Item>
)}

View File

@ -70,7 +70,7 @@ class UserListPage extends BaseListPage {
password: "123",
passwordSalt: "",
displayName: `New User - ${randomName}`,
avatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
avatar: this.state.organization.defaultAvatar ?? `${Setting.StaticBaseUrl}/img/casbin.svg`,
email: `${randomName}@example.com`,
phone: Setting.getRandomNumber(),
countryCode: this.state.organization.countryCodes?.length > 0 ? this.state.organization.countryCodes[0] : "",

View File

@ -62,6 +62,7 @@ const userTemplate = {
"name": "admin",
"createdTime": "2020-07-16T21:46:52+08:00",
"updatedTime": "",
"deletedTime": "",
"id": "9eb20f79-3bb5-4e74-99ac-39e3b9a171e8",
"type": "normal-user",
"password": "***",

View File

@ -98,7 +98,7 @@ class MfaSetupPage extends React.Component {
renderMfaTypeSwitch() {
const renderSmsLink = () => {
if (this.state.mfaType === SmsMfaType || this.props.account.mfaPhoneEnabled) {
if (this.state.mfaType === SmsMfaType) {
return null;
}
return (<Button type={"link"} onClick={() => {
@ -112,7 +112,7 @@ class MfaSetupPage extends React.Component {
};
const renderEmailLink = () => {
if (this.state.mfaType === EmailMfaType || this.props.account.mfaEmailEnabled) {
if (this.state.mfaType === EmailMfaType) {
return null;
}
return (<Button type={"link"} onClick={() => {
@ -126,7 +126,7 @@ class MfaSetupPage extends React.Component {
};
const renderTotpLink = () => {
if (this.state.mfaType === TotpMfaType || this.props.account.totpSecret !== "") {
if (this.state.mfaType === TotpMfaType) {
return null;
}
return (<Button type={"link"} onClick={() => {
@ -191,7 +191,9 @@ class MfaSetupPage extends React.Component {
onSuccess={() => {
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
this.props.onfinish();
if (localStorage.getItem("mfaRedirectUrl") !== null) {
const mfaRedirectUrl = localStorage.getItem("mfaRedirectUrl");
if (mfaRedirectUrl !== undefined && mfaRedirectUrl !== null) {
Setting.goToLink(localStorage.getItem("mfaRedirectUrl"));
localStorage.removeItem("mfaRedirectUrl");
} else {