* Add is_writable checkbox to deploy keys interface * Add writable key option to deploy key form * Add support for writable ssh keys in the interface * Rename IsWritable to ReadOnly * Test: create read-only and read-write deploy keys via api * Add DeployKey access mode migration * Update gitea sdk via govendor * Fix deploykey migration * Add unittests for writable deploy keys * Move template text to locale * Remove implicit column update * Remove duplicate locales * Replace ReadOnly field with IsReadOnly method * Fix deploy_keys related integration test * Rename v54 migration with v55 * Fix migration hell
This commit is contained in:
		
							parent
							
								
									70b6c07590
								
							
						
					
					
						commit
						e78786ef39
					
				
					 13 changed files with 184 additions and 13 deletions
				
			
		|  | @ -5,9 +5,11 @@ | ||||||
| package integrations | package integrations | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
| 	api "code.gitea.io/sdk/gitea" | 	api "code.gitea.io/sdk/gitea" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -37,3 +39,54 @@ func TestDeleteDeployKeyNoLogin(t *testing.T) { | ||||||
| 	req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo1/keys/1") | 	req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo1/keys/1") | ||||||
| 	MakeRequest(t, req, http.StatusUnauthorized) | 	MakeRequest(t, req, http.StatusUnauthorized) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestCreateReadOnlyDeployKey(t *testing.T) { | ||||||
|  | 	prepareTestEnv(t) | ||||||
|  | 	repo := models.AssertExistsAndLoadBean(t, &models.Repository{Name: "repo1"}).(*models.Repository) | ||||||
|  | 	repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) | ||||||
|  | 
 | ||||||
|  | 	session := loginUser(t, repoOwner.Name) | ||||||
|  | 
 | ||||||
|  | 	keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) | ||||||
|  | 	rawKeyBody := api.CreateKeyOption{ | ||||||
|  | 		Title:    "read-only", | ||||||
|  | 		Key:      "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n", | ||||||
|  | 		ReadOnly: true, | ||||||
|  | 	} | ||||||
|  | 	req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody) | ||||||
|  | 	resp := session.MakeRequest(t, req, http.StatusCreated) | ||||||
|  | 
 | ||||||
|  | 	var newDeployKey api.DeployKey | ||||||
|  | 	DecodeJSON(t, resp, &newDeployKey) | ||||||
|  | 	models.AssertExistsAndLoadBean(t, &models.DeployKey{ | ||||||
|  | 		ID:      newDeployKey.ID, | ||||||
|  | 		Name:    rawKeyBody.Title, | ||||||
|  | 		Content: rawKeyBody.Key, | ||||||
|  | 		Mode:    models.AccessModeRead, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestCreateReadWriteDeployKey(t *testing.T) { | ||||||
|  | 	prepareTestEnv(t) | ||||||
|  | 	repo := models.AssertExistsAndLoadBean(t, &models.Repository{Name: "repo1"}).(*models.Repository) | ||||||
|  | 	repoOwner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) | ||||||
|  | 
 | ||||||
|  | 	session := loginUser(t, repoOwner.Name) | ||||||
|  | 
 | ||||||
|  | 	keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) | ||||||
|  | 	rawKeyBody := api.CreateKeyOption{ | ||||||
|  | 		Title: "read-write", | ||||||
|  | 		Key:   "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDsufOCrDDlT8DLkodnnJtbq7uGflcPae7euTfM+Laq4So+v4WeSV362Rg0O/+Sje1UthrhN6lQkfRkdWIlCRQEXg+LMqr6RhvDfZquE2Xwqv/itlz7LjbdAUdYoO1iH7rMSmYvQh4WEnC/DAacKGbhdGIM/ZBz0z6tHm7bPgbI9ykEKekTmPwQFP1Qebvf5NYOFMWqQ2sCEAI9dBMVLoojsIpV+KADf+BotiIi8yNfTG2rzmzpxBpW9fYjd1Sy1yd4NSUpoPbEJJYJ1TrjiSWlYOVq9Ar8xW1O87i6gBjL/3zN7ANeoYhaAXupdOS6YL22YOK/yC0tJtXwwdh/eSrh", | ||||||
|  | 	} | ||||||
|  | 	req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody) | ||||||
|  | 	resp := session.MakeRequest(t, req, http.StatusCreated) | ||||||
|  | 
 | ||||||
|  | 	var newDeployKey api.DeployKey | ||||||
|  | 	DecodeJSON(t, resp, &newDeployKey) | ||||||
|  | 	models.AssertExistsAndLoadBean(t, &models.DeployKey{ | ||||||
|  | 		ID:      newDeployKey.ID, | ||||||
|  | 		Name:    rawKeyBody.Title, | ||||||
|  | 		Content: rawKeyBody.Key, | ||||||
|  | 		Mode:    models.AccessModeWrite, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								models/fixtures/deploy_key.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								models/fixtures/deploy_key.yml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | [] # empty | ||||||
|  | @ -162,6 +162,8 @@ var migrations = []Migration{ | ||||||
| 	NewMigration("add reactions", addReactions), | 	NewMigration("add reactions", addReactions), | ||||||
| 	// v54 -> v55
 | 	// v54 -> v55
 | ||||||
| 	NewMigration("add pull request options", addPullRequestOptions), | 	NewMigration("add pull request options", addPullRequestOptions), | ||||||
|  | 	// v55 -> v56
 | ||||||
|  | 	NewMigration("add writable deploy keys", addModeToDeploKeys), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Migrate database to current version
 | // Migrate database to current version
 | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								models/migrations/v55.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								models/migrations/v55.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved.
 | ||||||
|  | // Use of this source code is governed by a MIT-style
 | ||||||
|  | // license that can be found in the LICENSE file.
 | ||||||
|  | 
 | ||||||
|  | package migrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"github.com/go-xorm/xorm" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func addModeToDeploKeys(x *xorm.Engine) error { | ||||||
|  | 	type DeployKey struct { | ||||||
|  | 		Mode models.AccessMode `xorm:"NOT NULL DEFAULT 1"` | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := x.Sync2(new(DeployKey)); err != nil { | ||||||
|  | 		return fmt.Errorf("Sync2: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -600,6 +600,8 @@ type DeployKey struct { | ||||||
| 	Fingerprint string | 	Fingerprint string | ||||||
| 	Content     string `xorm:"-"` | 	Content     string `xorm:"-"` | ||||||
| 
 | 
 | ||||||
|  | 	Mode AccessMode `xorm:"NOT NULL DEFAULT 1"` | ||||||
|  | 
 | ||||||
| 	CreatedUnix       util.TimeStamp `xorm:"created"` | 	CreatedUnix       util.TimeStamp `xorm:"created"` | ||||||
| 	UpdatedUnix       util.TimeStamp `xorm:"updated"` | 	UpdatedUnix       util.TimeStamp `xorm:"updated"` | ||||||
| 	HasRecentActivity bool           `xorm:"-"` | 	HasRecentActivity bool           `xorm:"-"` | ||||||
|  | @ -622,6 +624,11 @@ func (key *DeployKey) GetContent() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // IsReadOnly checks if the key can only be used for read operations
 | ||||||
|  | func (key *DeployKey) IsReadOnly() bool { | ||||||
|  | 	return key.Mode == AccessModeRead | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func checkDeployKey(e Engine, keyID, repoID int64, name string) error { | func checkDeployKey(e Engine, keyID, repoID int64, name string) error { | ||||||
| 	// Note: We want error detail, not just true or false here.
 | 	// Note: We want error detail, not just true or false here.
 | ||||||
| 	has, err := e. | 	has, err := e. | ||||||
|  | @ -646,7 +653,7 @@ func checkDeployKey(e Engine, keyID, repoID int64, name string) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // addDeployKey adds new key-repo relation.
 | // addDeployKey adds new key-repo relation.
 | ||||||
| func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) { | func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string, mode AccessMode) (*DeployKey, error) { | ||||||
| 	if err := checkDeployKey(e, keyID, repoID, name); err != nil { | 	if err := checkDeployKey(e, keyID, repoID, name); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -656,6 +663,7 @@ func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string | ||||||
| 		RepoID:      repoID, | 		RepoID:      repoID, | ||||||
| 		Name:        name, | 		Name:        name, | ||||||
| 		Fingerprint: fingerprint, | 		Fingerprint: fingerprint, | ||||||
|  | 		Mode:        mode, | ||||||
| 	} | 	} | ||||||
| 	_, err := e.Insert(key) | 	_, err := e.Insert(key) | ||||||
| 	return key, err | 	return key, err | ||||||
|  | @ -670,15 +678,20 @@ func HasDeployKey(keyID, repoID int64) bool { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AddDeployKey add new deploy key to database and authorized_keys file.
 | // AddDeployKey add new deploy key to database and authorized_keys file.
 | ||||||
| func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) { | func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey, error) { | ||||||
| 	fingerprint, err := calcFingerprint(content) | 	fingerprint, err := calcFingerprint(content) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	accessMode := AccessModeRead | ||||||
|  | 	if !readOnly { | ||||||
|  | 		accessMode = AccessModeWrite | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	pkey := &PublicKey{ | 	pkey := &PublicKey{ | ||||||
| 		Fingerprint: fingerprint, | 		Fingerprint: fingerprint, | ||||||
| 		Mode:        AccessModeRead, | 		Mode:        accessMode, | ||||||
| 		Type:        KeyTypeDeploy, | 		Type:        KeyTypeDeploy, | ||||||
| 	} | 	} | ||||||
| 	has, err := x.Get(pkey) | 	has, err := x.Get(pkey) | ||||||
|  | @ -701,7 +714,7 @@ func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint) | 	key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint, accessMode) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("addDeployKey: %v", err) | 		return nil, fmt.Errorf("addDeployKey: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -172,6 +172,7 @@ type AddKeyForm struct { | ||||||
| 	Type       string `binding:"OmitEmpty"` | 	Type       string `binding:"OmitEmpty"` | ||||||
| 	Title      string `binding:"Required;MaxSize(50)"` | 	Title      string `binding:"Required;MaxSize(50)"` | ||||||
| 	Content    string `binding:"Required"` | 	Content    string `binding:"Required"` | ||||||
|  | 	IsWritable bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Validate validates the fields
 | // Validate validates the fields
 | ||||||
|  |  | ||||||
|  | @ -401,6 +401,8 @@ valid_until = Valid until | ||||||
| valid_forever = Valid forever | valid_forever = Valid forever | ||||||
| last_used = Last used on | last_used = Last used on | ||||||
| no_activity = No recent activity | no_activity = No recent activity | ||||||
|  | can_read_info = Read | ||||||
|  | can_write_info = Write | ||||||
| key_state_desc = This key has been used in the last 7 days | key_state_desc = This key has been used in the last 7 days | ||||||
| token_state_desc = This token has been used in the last 7 days | token_state_desc = This token has been used in the last 7 days | ||||||
| show_openid = Show on profile | show_openid = Show on profile | ||||||
|  | @ -995,6 +997,8 @@ settings.add_dingtalk_hook_desc = Add <a href="%s">Dingtalk</a> integration to y | ||||||
| settings.deploy_keys = Deploy Keys | settings.deploy_keys = Deploy Keys | ||||||
| settings.add_deploy_key = Add Deploy Key | settings.add_deploy_key = Add Deploy Key | ||||||
| settings.deploy_key_desc = Deploy keys have read-only access. They are not the same as personal account SSH keys. | settings.deploy_key_desc = Deploy keys have read-only access. They are not the same as personal account SSH keys. | ||||||
|  | settings.is_writable = Allow write access | ||||||
|  | settings.is_writable_info = Can this key be used to <strong>push</strong> to this repository? Deploy keys always have pull access. | ||||||
| settings.no_deploy_keys = You haven't added any deploy keys. | settings.no_deploy_keys = You haven't added any deploy keys. | ||||||
| settings.title = Title | settings.title = Title | ||||||
| settings.deploy_key_content = Content | settings.deploy_key_content = Content | ||||||
|  |  | ||||||
|  | @ -160,7 +160,7 @@ func CreateDeployKey(ctx *context.APIContext, form api.CreateKeyOption) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	key, err := models.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content) | 	key, err := models.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, form.ReadOnly) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		HandleAddKeyError(ctx, err) | 		HandleAddKeyError(ctx, err) | ||||||
| 		return | 		return | ||||||
|  |  | ||||||
|  | @ -544,7 +544,7 @@ func DeployKeysPost(ctx *context.Context, form auth.AddKeyForm) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	key, err := models.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content) | 	key, err := models.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, !form.IsWritable) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Data["HasError"] = true | 		ctx.Data["HasError"] = true | ||||||
| 		switch { | 		switch { | ||||||
|  |  | ||||||
							
								
								
									
										61
									
								
								routers/repo/settings_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								routers/repo/settings_test.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | ||||||
|  | // Copyright 2017 The Gitea Authors. All rights reserved.
 | ||||||
|  | // Use of this source code is governed by a MIT-style
 | ||||||
|  | // license that can be found in the LICENSE file.
 | ||||||
|  | 
 | ||||||
|  | package repo | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/modules/auth" | ||||||
|  | 	"code.gitea.io/gitea/modules/test" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestAddReadOnlyDeployKey(t *testing.T) { | ||||||
|  | 	models.PrepareTestEnv(t) | ||||||
|  | 
 | ||||||
|  | 	ctx := test.MockContext(t, "user2/repo1/settings/keys") | ||||||
|  | 
 | ||||||
|  | 	test.LoadUser(t, ctx, 2) | ||||||
|  | 	test.LoadRepo(t, ctx, 2) | ||||||
|  | 
 | ||||||
|  | 	addKeyForm := auth.AddKeyForm{ | ||||||
|  | 		Title:   "read-only", | ||||||
|  | 		Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n", | ||||||
|  | 	} | ||||||
|  | 	DeployKeysPost(ctx, addKeyForm) | ||||||
|  | 	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) | ||||||
|  | 
 | ||||||
|  | 	models.AssertExistsAndLoadBean(t, &models.DeployKey{ | ||||||
|  | 		Name:    addKeyForm.Title, | ||||||
|  | 		Content: addKeyForm.Content, | ||||||
|  | 		Mode:    models.AccessModeRead, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAddReadWriteOnlyDeployKey(t *testing.T) { | ||||||
|  | 	models.PrepareTestEnv(t) | ||||||
|  | 
 | ||||||
|  | 	ctx := test.MockContext(t, "user2/repo1/settings/keys") | ||||||
|  | 
 | ||||||
|  | 	test.LoadUser(t, ctx, 2) | ||||||
|  | 	test.LoadRepo(t, ctx, 2) | ||||||
|  | 
 | ||||||
|  | 	addKeyForm := auth.AddKeyForm{ | ||||||
|  | 		Title:      "read-write", | ||||||
|  | 		Content:    "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n", | ||||||
|  | 		IsWritable: true, | ||||||
|  | 	} | ||||||
|  | 	DeployKeysPost(ctx, addKeyForm) | ||||||
|  | 	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) | ||||||
|  | 
 | ||||||
|  | 	models.AssertExistsAndLoadBean(t, &models.DeployKey{ | ||||||
|  | 		Name:    addKeyForm.Title, | ||||||
|  | 		Content: addKeyForm.Content, | ||||||
|  | 		Mode:    models.AccessModeWrite, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | @ -31,7 +31,7 @@ | ||||||
| 										{{.Fingerprint}} | 										{{.Fingerprint}} | ||||||
| 									</div> | 									</div> | ||||||
| 									<div class="activity meta"> | 									<div class="activity meta"> | ||||||
| 										<i>{{$.i18n.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> —  <i class="octicon octicon-info"></i> {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{.UpdatedUnix.FormatShort}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i> | 										<i>{{$.i18n.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> —  <i class="octicon octicon-info"></i> {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{.UpdatedUnix.FormatShort}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}} - <span>{{$.i18n.Tr "settings.can_read_info"}}{{if not .IsReadOnly}} / {{$.i18n.Tr "settings.can_write_info"}} {{end}}</i> | ||||||
| 									</div> | 									</div> | ||||||
| 								</div> | 								</div> | ||||||
| 						</div> | 						</div> | ||||||
|  | @ -60,6 +60,15 @@ | ||||||
| 						<label for="content">{{.i18n.Tr "repo.settings.deploy_key_content"}}</label> | 						<label for="content">{{.i18n.Tr "repo.settings.deploy_key_content"}}</label> | ||||||
| 						<textarea id="ssh-key-content" name="content" required>{{.content}}</textarea> | 						<textarea id="ssh-key-content" name="content" required>{{.content}}</textarea> | ||||||
| 					</div> | 					</div> | ||||||
|  | 					<div class="field"> | ||||||
|  | 						<div class="ui checkbox {{if .Err_IsWritable}}error{{end}}"> | ||||||
|  | 							<input id="ssh-key-is-writable" name="is_writable" class="hidden" type="checkbox" value="1"> | ||||||
|  | 							<label for="is_writable"> | ||||||
|  | 								{{.i18n.Tr "repo.settings.is_writable"}} | ||||||
|  | 							</label> | ||||||
|  | 							<small style="padding-left: 26px;">{{$.i18n.Tr "repo.settings.is_writable_info" | Str2html}}</small> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
| 					<button class="ui green button"> | 					<button class="ui green button"> | ||||||
| 						{{.i18n.Tr "repo.settings.add_deploy_key"}} | 						{{.i18n.Tr "repo.settings.add_deploy_key"}} | ||||||
| 					</button> | 					</button> | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								vendor/code.gitea.io/sdk/gitea/repo_key.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/code.gitea.io/sdk/gitea/repo_key.go
									
									
									
										generated
									
									
										vendored
									
									
								
							|  | @ -46,6 +46,10 @@ type CreateKeyOption struct { | ||||||
| 	// required: true
 | 	// required: true
 | ||||||
| 	// unique: true
 | 	// unique: true
 | ||||||
| 	Key string `json:"key" binding:"Required"` | 	Key string `json:"key" binding:"Required"` | ||||||
|  | 	// Describe if the key has only read access or read/write
 | ||||||
|  | 	//
 | ||||||
|  | 	// required: false
 | ||||||
|  | 	ReadOnly bool `json:"read_only"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CreateDeployKey options when create one deploy key
 | // CreateDeployKey options when create one deploy key
 | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							|  | @ -9,10 +9,10 @@ | ||||||
| 			"revisionTime": "2017-12-22T02:43:26Z" | 			"revisionTime": "2017-12-22T02:43:26Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "QQ7g7B9+EIzGjO14KCGEs9TNEzM=", | 			"checksumSHA1": "Qtq0kW+BnpYMOriaoCjMa86WGG8=", | ||||||
| 			"path": "code.gitea.io/sdk/gitea", | 			"path": "code.gitea.io/sdk/gitea", | ||||||
| 			"revision": "ec7d3af43b598c1a3f2cb12f633b9625649d8e54", | 			"revision": "79eee8f12c7fc1cc5b802c5cdc5b494ef3733866", | ||||||
| 			"revisionTime": "2017-11-28T12:30:39Z" | 			"revisionTime": "2017-12-20T06:57:50Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", | 			"checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue