Skip to content

Commit 4ca803f

Browse files
committed
Disable SSH key addition and deletion when externally managed
When a user has a login source which has SSH key management key addition and deletion using the UI should be disabled. Fix #13983 Signed-off-by: Andrew Thornton <[email protected]>
1 parent c3fc190 commit 4ca803f

File tree

4 files changed

+112
-18
lines changed

4 files changed

+112
-18
lines changed

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ principal_state_desc = This principal has been used in the last 7 days
556556
show_openid = Show on profile
557557
hide_openid = Hide from profile
558558
ssh_disabled = SSH Disabled
559+
ssh_externally_managed = SSH keys are externally managed for this user
559560
manage_social = Manage Associated Social Accounts
560561
social_desc = These social accounts are linked to your Gitea account. Make sure you recognize all of them as they can be used to sign in to your Gitea account.
561562
unbind = Unlink

routers/api/v1/user/key.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package user
66

77
import (
88
"net/http"
9+
"strings"
910

1011
"code.gitea.io/gitea/models"
1112
"code.gitea.io/gitea/modules/context"
@@ -201,6 +202,25 @@ func GetPublicKey(ctx *context.APIContext) {
201202
ctx.JSON(http.StatusOK, apiKey)
202203
}
203204

205+
func sshKeysExternallyManaged(user *models.User) (bool, error) {
206+
if user.LoginSource == 0 {
207+
return false, nil
208+
}
209+
source, err := models.GetLoginSourceByID(user.LoginSource)
210+
if err != nil {
211+
return false, err
212+
}
213+
ldapSource := source.LDAP()
214+
if ldapSource != nil &&
215+
source.IsSyncEnabled &&
216+
(source.Type == models.LoginLDAP || source.Type == models.LoginDLDAP) &&
217+
len(strings.TrimSpace(ldapSource.AttributeSSHPublicKey)) > 0 {
218+
// Disable setting SSH keys for this user
219+
return true, nil
220+
}
221+
return false, nil
222+
}
223+
204224
// CreateUserPublicKey creates new public key to given user by ID.
205225
func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
206226
content, err := models.CheckPublicKeyString(form.Key)
@@ -209,6 +229,18 @@ func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid
209229
return
210230
}
211231

232+
user, err := models.GetUserByID(uid)
233+
if err != nil {
234+
ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
235+
}
236+
externallyManaged, err := sshKeysExternallyManaged(user)
237+
if err != nil {
238+
ctx.Error(http.StatusInternalServerError, "sshKeysExternallyManaged", err)
239+
}
240+
if externallyManaged {
241+
ctx.Error(http.StatusForbidden, "", "SSH Keys are externally managed for this user")
242+
}
243+
212244
key, err := models.AddPublicKey(uid, form.Title, content, 0)
213245
if err != nil {
214246
repo.HandleAddKeyError(ctx, err)
@@ -266,6 +298,13 @@ func DeletePublicKey(ctx *context.APIContext) {
266298
// "$ref": "#/responses/forbidden"
267299
// "404":
268300
// "$ref": "#/responses/notFound"
301+
externallyManaged, err := sshKeysExternallyManaged(ctx.User)
302+
if err != nil {
303+
ctx.Error(http.StatusInternalServerError, "sshKeysExternallyManaged", err)
304+
}
305+
if externallyManaged {
306+
ctx.Error(http.StatusForbidden, "", "SSH Keys are externally managed for this user")
307+
}
269308

270309
if err := models.DeletePublicKey(ctx.User, ctx.ParamsInt64(":id")); err != nil {
271310
if models.IsErrKeyNotExist(err) {

routers/user/setting/keys.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package setting
77

88
import (
9+
"strings"
10+
911
"code.gitea.io/gitea/models"
1012
"code.gitea.io/gitea/modules/auth"
1113
"code.gitea.io/gitea/modules/base"
@@ -30,6 +32,25 @@ func Keys(ctx *context.Context) {
3032
ctx.HTML(200, tplSettingsKeys)
3133
}
3234

35+
func sshKeysExternallyManaged(user *models.User) (bool, error) {
36+
if user.LoginSource == 0 {
37+
return false, nil
38+
}
39+
source, err := models.GetLoginSourceByID(user.LoginSource)
40+
if err != nil {
41+
return false, err
42+
}
43+
ldapSource := source.LDAP()
44+
if ldapSource != nil &&
45+
source.IsSyncEnabled &&
46+
(source.Type == models.LoginLDAP || source.Type == models.LoginDLDAP) &&
47+
len(strings.TrimSpace(ldapSource.AttributeSSHPublicKey)) > 0 {
48+
// Disable setting SSH keys for this user
49+
return true, nil
50+
}
51+
return false, nil
52+
}
53+
3354
// KeysPost response for change user's SSH/GPG keys
3455
func KeysPost(ctx *context.Context, form auth.AddKeyForm) {
3556
ctx.Data["Title"] = ctx.Tr("settings")
@@ -105,6 +126,16 @@ func KeysPost(ctx *context.Context, form auth.AddKeyForm) {
105126
ctx.Flash.Success(ctx.Tr("settings.add_gpg_key_success", keyIDs))
106127
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
107128
case "ssh":
129+
external, err := sshKeysExternallyManaged(ctx.User)
130+
if err != nil {
131+
ctx.ServerError("sshKeysExternalManaged", err)
132+
return
133+
}
134+
if external {
135+
ctx.Flash.Error(ctx.Tr("setting.ssh_externally_managed"))
136+
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
137+
return
138+
}
108139
content, err := models.CheckPublicKeyString(form.Content)
109140
if err != nil {
110141
if models.IsErrSSHDisabled(err) {
@@ -160,6 +191,16 @@ func DeleteKey(ctx *context.Context) {
160191
ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
161192
}
162193
case "ssh":
194+
external, err := sshKeysExternallyManaged(ctx.User)
195+
if err != nil {
196+
ctx.ServerError("sshKeysExternalManaged", err)
197+
return
198+
}
199+
if external {
200+
ctx.Flash.Error(ctx.Tr("setting.ssh_externally_managed"))
201+
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
202+
return
203+
}
163204
if err := models.DeletePublicKey(ctx.User, ctx.QueryInt64("id")); err != nil {
164205
ctx.Flash.Error("DeletePublicKey: " + err.Error())
165206
} else {
@@ -181,6 +222,15 @@ func DeleteKey(ctx *context.Context) {
181222
}
182223

183224
func loadKeysData(ctx *context.Context) {
225+
external, err := sshKeysExternallyManaged(ctx.User)
226+
if err != nil {
227+
ctx.ServerError("sshKeysExternalManaged", err)
228+
return
229+
}
230+
if external {
231+
ctx.Data["SSHKeysExternalManaged"] = true
232+
}
233+
184234
keys, err := models.ListPublicKeys(ctx.User.ID, models.ListOptions{})
185235
if err != nil {
186236
ctx.ServerError("ListPublicKeys", err)

templates/user/settings/keys_ssh.tmpl

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<h4 class="ui top attached header">
22
{{.i18n.Tr "settings.manage_ssh_keys"}}
33
<div class="ui right">
4-
{{if not .DisableSSH}}
4+
{{if not ( or .DisableSSH .SSHKeysExternalManaged ) }}
55
<div class="ui blue tiny show-panel button" data-panel="#add-ssh-key-panel">{{.i18n.Tr "settings.add_key"}}</div>
6+
{{else if .SSHKeysExternalManaged}}
7+
<div class="ui blue tiny button disabled">{{.i18n.Tr "settings.ssh_externally_managed"}}</div>
68
{{else}}
79
<div class="ui blue tiny button disabled">{{.i18n.Tr "settings.ssh_disabled"}}</div>
810
{{end}}
@@ -15,23 +17,25 @@
1517
</div>
1618
{{range .Keys}}
1719
<div class="item">
18-
<div class="right floated content">
19-
<button class="ui red tiny button delete-button" id="delete-ssh" data-url="{{$.Link}}/delete?type=ssh" data-id="{{.ID}}">
20-
{{$.i18n.Tr "settings.delete_key"}}
21-
</button>
22-
</div>
23-
<div class="left floated content">
24-
<span class="{{if .HasRecentActivity}}green{{end}}" {{if .HasRecentActivity}}data-content="{{$.i18n.Tr "settings.key_state_desc"}}" data-variation="inverted tiny"{{end}}>{{svg "octicon-key" 32}}</span>
25-
</div>
26-
<div class="content">
27-
<strong>{{.Name}}</strong>
28-
<div class="print meta">
29-
{{.Fingerprint}}
30-
</div>
31-
<div class="activity meta">
32-
<i>{{$.i18n.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{.UpdatedUnix.FormatShort}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i>
33-
</div>
34-
</div>
20+
{{if not $.SSHKeysExternalManaged}}
21+
<div class="right floated content">
22+
<button class="ui red tiny button delete-button" id="delete-ssh" data-url="{{$.Link}}/delete?type=ssh" data-id="{{.ID}}">
23+
{{$.i18n.Tr "settings.delete_key"}}
24+
</button>
25+
</div>
26+
{{end}}
27+
<div class="left floated content">
28+
<span class="{{if .HasRecentActivity}}green{{end}}" {{if .HasRecentActivity}}data-content="{{$.i18n.Tr "settings.key_state_desc"}}" data-variation="inverted tiny"{{end}}>{{svg "octicon-key" 32}}</span>
29+
</div>
30+
<div class="content">
31+
<strong>{{.Name}}</strong>
32+
<div class="print meta">
33+
{{.Fingerprint}}
34+
</div>
35+
<div class="activity meta">
36+
<i>{{$.i18n.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{.UpdatedUnix.FormatShort}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i>
37+
</div>
38+
</div>
3539
</div>
3640
{{end}}
3741
</div>

0 commit comments

Comments
 (0)