#334: Add Deployment Key Support
This commit is contained in:
parent
9f12ab0e88
commit
39a3b768bc
26 changed files with 693 additions and 149 deletions
|
@ -10,8 +10,8 @@ github.com/Unknwon/macaron = commit:635c89ac74
|
||||||
github.com/Unknwon/paginater = commit:cab2d086fa
|
github.com/Unknwon/paginater = commit:cab2d086fa
|
||||||
github.com/codegangsta/cli = commit:2bcd11f863
|
github.com/codegangsta/cli = commit:2bcd11f863
|
||||||
github.com/go-sql-driver/mysql = commit:a197e5d405
|
github.com/go-sql-driver/mysql = commit:a197e5d405
|
||||||
github.com/go-xorm/core = commit:bacc62db6e
|
github.com/go-xorm/core =
|
||||||
github.com/go-xorm/xorm = commit:7b8945acfe
|
github.com/go-xorm/xorm =
|
||||||
github.com/gogits/chardet = commit:2404f77725
|
github.com/gogits/chardet = commit:2404f77725
|
||||||
github.com/gogits/go-gogs-client = commit:92e76d616a
|
github.com/gogits/go-gogs-client = commit:92e76d616a
|
||||||
github.com/lib/pq = commit:0dad96c0b9
|
github.com/lib/pq = commit:0dad96c0b9
|
||||||
|
|
45
cmd/serve.go
45
cmd/serve.go
|
@ -71,17 +71,17 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func fail(userMessage, logMessage string, args ...interface{}) {
|
||||||
|
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
|
||||||
|
log.GitLogger.Fatal(3, logMessage, args...)
|
||||||
|
}
|
||||||
|
|
||||||
func runServ(c *cli.Context) {
|
func runServ(c *cli.Context) {
|
||||||
if c.IsSet("config") {
|
if c.IsSet("config") {
|
||||||
setting.CustomConf = c.String("config")
|
setting.CustomConf = c.String("config")
|
||||||
}
|
}
|
||||||
setup("serv.log")
|
setup("serv.log")
|
||||||
|
|
||||||
fail := func(userMessage, logMessage string, args ...interface{}) {
|
|
||||||
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
|
|
||||||
log.GitLogger.Fatal(3, logMessage, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.Args()) < 1 {
|
if len(c.Args()) < 1 {
|
||||||
fail("Not enough arguments", "Not enough arguments")
|
fail("Not enough arguments", "Not enough arguments")
|
||||||
}
|
}
|
||||||
|
@ -131,15 +131,37 @@ func runServ(c *cli.Context) {
|
||||||
if requestedMode == models.ACCESS_MODE_WRITE || repo.IsPrivate {
|
if requestedMode == models.ACCESS_MODE_WRITE || repo.IsPrivate {
|
||||||
keys := strings.Split(c.Args()[0], "-")
|
keys := strings.Split(c.Args()[0], "-")
|
||||||
if len(keys) != 2 {
|
if len(keys) != 2 {
|
||||||
fail("key-id format error", "Invalid key id: %s", c.Args()[0])
|
fail("Key ID format error", "Invalid key ID: %s", c.Args()[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
keyID, err = com.StrTo(keys[1]).Int64()
|
key, err := models.GetPublicKeyByID(com.StrTo(keys[1]).MustInt64())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail("key-id format error", "Invalid key id: %s", err)
|
fail("Key ID format error", "Invalid key ID[%s]: %v", c.Args()[0], err)
|
||||||
|
}
|
||||||
|
keyID = key.ID
|
||||||
|
|
||||||
|
// Check deploy key or user key.
|
||||||
|
if key.Type == models.KEY_TYPE_DEPLOY {
|
||||||
|
if key.Mode < requestedMode {
|
||||||
|
fail("Key permission denied", "Cannot push with deployment key: %d", key.ID)
|
||||||
|
}
|
||||||
|
// Check if this deploy key belongs to current repository.
|
||||||
|
if !models.HasDeployKey(key.ID, repo.Id) {
|
||||||
|
fail("Key access denied", "Key access denied: %d-%d", key.ID, repo.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err = models.GetUserByKeyId(keyID)
|
// Update deploy key activity.
|
||||||
|
deployKey, err := models.GetDeployKeyByRepo(key.ID, repo.Id)
|
||||||
|
if err != nil {
|
||||||
|
fail("Internal error", "GetDeployKey: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployKey.Updated = time.Now()
|
||||||
|
if err = models.UpdateDeployKey(deployKey); err != nil {
|
||||||
|
fail("Internal error", "UpdateDeployKey: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user, err = models.GetUserByKeyId(key.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
|
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
|
||||||
}
|
}
|
||||||
|
@ -157,6 +179,7 @@ func runServ(c *cli.Context) {
|
||||||
user.Name, requestedMode, repoPath)
|
user.Name, requestedMode, repoPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uuid := uuid.NewV4().String()
|
uuid := uuid.NewV4().String()
|
||||||
os.Setenv("uuid", uuid)
|
os.Setenv("uuid", uuid)
|
||||||
|
@ -201,9 +224,9 @@ func runServ(c *cli.Context) {
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update key activity.
|
// Update user key activity.
|
||||||
if keyID > 0 {
|
if keyID > 0 {
|
||||||
key, err := models.GetPublicKeyById(keyID)
|
key, err := models.GetPublicKeyByID(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail("Internal error", "GetPublicKeyById: %v", err)
|
fail("Internal error", "GetPublicKeyById: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -404,6 +404,13 @@ func runWeb(ctx *cli.Context) {
|
||||||
m.Get("/:name", repo.GitHooksEdit)
|
m.Get("/:name", repo.GitHooksEdit)
|
||||||
m.Post("/:name", repo.GitHooksEditPost)
|
m.Post("/:name", repo.GitHooksEditPost)
|
||||||
}, middleware.GitHookService())
|
}, middleware.GitHookService())
|
||||||
|
|
||||||
|
m.Group("/keys", func() {
|
||||||
|
m.Combo("").Get(repo.SettingsDeployKeys).
|
||||||
|
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.SettingsDeployKeysPost)
|
||||||
|
m.Post("/delete", repo.DeleteDeployKey)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
}, reqSignIn, middleware.RepoAssignment(true), reqRepoAdmin)
|
}, reqSignIn, middleware.RepoAssignment(true), reqRepoAdmin)
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,6 @@ repo_name_been_taken = Repository name has been already taken.
|
||||||
org_name_been_taken = Organization name has been already taken.
|
org_name_been_taken = Organization name has been already taken.
|
||||||
team_name_been_taken = Team name has been already taken.
|
team_name_been_taken = Team name has been already taken.
|
||||||
email_been_used = E-mail address has been already used.
|
email_been_used = E-mail address has been already used.
|
||||||
ssh_key_been_used = Public key name or content has been used.
|
|
||||||
illegal_team_name = Team name contains illegal characters.
|
illegal_team_name = Team name contains illegal characters.
|
||||||
username_password_incorrect = Username or password is not correct.
|
username_password_incorrect = Username or password is not correct.
|
||||||
enterred_invalid_repo_name = Please make sure that the repository name you entered is correct.
|
enterred_invalid_repo_name = Please make sure that the repository name you entered is correct.
|
||||||
|
@ -264,13 +263,16 @@ add_key = Add Key
|
||||||
ssh_desc = This is a list of SSH keys associated with your account. As these keys allow anyone using them to gain access to your repositories, it is highly important that you make sure you recognize them.
|
ssh_desc = This is a list of SSH keys associated with your account. As these keys allow anyone using them to gain access to your repositories, it is highly important that you make sure you recognize them.
|
||||||
ssh_helper = <strong>Don't know how?</strong> Check out GitHub's guide to <a href="%s">create your own SSH keys</a> or solve <a href="%s">common problems</a> you might encounter using SSH.
|
ssh_helper = <strong>Don't know how?</strong> Check out GitHub's guide to <a href="%s">create your own SSH keys</a> or solve <a href="%s">common problems</a> you might encounter using SSH.
|
||||||
add_new_key = Add SSH Key
|
add_new_key = Add SSH Key
|
||||||
|
ssh_key_been_used = Public key content has been used.
|
||||||
|
ssh_key_name_used = Public key with same name has already existed.
|
||||||
key_name = Key Name
|
key_name = Key Name
|
||||||
key_content = Content
|
key_content = Content
|
||||||
add_key_success = New SSH Key has been added!
|
add_key_success = New SSH key '%s' has been added successfully!
|
||||||
delete_key = Delete
|
delete_key = Delete
|
||||||
add_on = Added on
|
add_on = Added on
|
||||||
last_used = Last used on
|
last_used = Last used on
|
||||||
no_activity = No recent activity
|
no_activity = No recent activity
|
||||||
|
key_state_desc = This key is used in last 7 days
|
||||||
|
|
||||||
manage_social = Manage Associated Social Accounts
|
manage_social = Manage Associated Social Accounts
|
||||||
social_desc = This is a list of associated social accounts. Remove any binding that you do not recognize.
|
social_desc = This is a list of associated social accounts. Remove any binding that you do not recognize.
|
||||||
|
@ -390,7 +392,7 @@ issues.label_edit = Edit
|
||||||
issues.label_delete = Delete
|
issues.label_delete = Delete
|
||||||
issues.label_modify = Label Modification
|
issues.label_modify = Label Modification
|
||||||
issues.label_deletion = Label Deletion
|
issues.label_deletion = Label Deletion
|
||||||
issues.label_deletion_desc = Delete label will remove its information in all related issues. Do you want to continue?
|
issues.label_deletion_desc = Delete this label will remove its information in all related issues. Do you want to continue?
|
||||||
issues.label_deletion_success = Label has been deleted successfully!
|
issues.label_deletion_success = Label has been deleted successfully!
|
||||||
|
|
||||||
milestones.new = New Milestone
|
milestones.new = New Milestone
|
||||||
|
@ -414,7 +416,7 @@ milestones.cancel = Cancel
|
||||||
milestones.modify = Modify Milestone
|
milestones.modify = Modify Milestone
|
||||||
milestones.edit_success = Changes of milestone '%s' has been saved successfully!
|
milestones.edit_success = Changes of milestone '%s' has been saved successfully!
|
||||||
milestones.deletion = Milestone Deletion
|
milestones.deletion = Milestone Deletion
|
||||||
milestones.deletion_desc = Delete milestone will remove its information in all related issues. Do you want to continue?
|
milestones.deletion_desc = Delete this milestone will remove its information in all related issues. Do you want to continue?
|
||||||
milestones.deletion_success = Milestone has been deleted successfully!
|
milestones.deletion_success = Milestone has been deleted successfully!
|
||||||
|
|
||||||
settings = Settings
|
settings = Settings
|
||||||
|
@ -422,7 +424,6 @@ settings.options = Options
|
||||||
settings.collaboration = Collaboration
|
settings.collaboration = Collaboration
|
||||||
settings.hooks = Webhooks
|
settings.hooks = Webhooks
|
||||||
settings.githooks = Git Hooks
|
settings.githooks = Git Hooks
|
||||||
settings.deploy_keys = Deploy Keys
|
|
||||||
settings.basic_settings = Basic Settings
|
settings.basic_settings = Basic Settings
|
||||||
settings.danger_zone = Danger Zone
|
settings.danger_zone = Danger Zone
|
||||||
settings.site = Official Site
|
settings.site = Official Site
|
||||||
|
@ -470,6 +471,17 @@ settings.add_slack_hook_desc = Add <a href="%s">Slack</a> integration to your re
|
||||||
settings.slack_token = Token
|
settings.slack_token = Token
|
||||||
settings.slack_domain = Domain
|
settings.slack_domain = Domain
|
||||||
settings.slack_channel = Channel
|
settings.slack_channel = Channel
|
||||||
|
settings.deploy_keys = Deploy Keys
|
||||||
|
settings.add_deploy_key = Add Deploy Key
|
||||||
|
settings.no_deploy_keys = You haven't added any deploy key.
|
||||||
|
settings.title = Title
|
||||||
|
settings.deploy_key_content = Content
|
||||||
|
settings.key_been_used = Deploy key content has been used.
|
||||||
|
settings.key_name_used = Deploy key with same name has already existed.
|
||||||
|
settings.add_key_success = New deploy key '%s' has been added successfully!
|
||||||
|
settings.deploy_key_deletion = Delete Deploy Key
|
||||||
|
settings.deploy_key_deletion_desc = Delete this deploy key will remove all related accesses for this repository. Do you want to continue?
|
||||||
|
settings.deploy_key_deletion_success = Deploy key has been deleted successfully!
|
||||||
|
|
||||||
diff.browse_source = Browse Source
|
diff.browse_source = Browse Source
|
||||||
diff.parent = parent
|
diff.parent = parent
|
||||||
|
|
2
gogs.go
2
gogs.go
|
@ -17,7 +17,7 @@ import (
|
||||||
"github.com/gogits/gogs/modules/setting"
|
"github.com/gogits/gogs/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APP_VER = "0.6.4.0805 Beta"
|
const APP_VER = "0.6.4.0806 Beta"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
|
@ -107,6 +107,82 @@ func (err ErrUserHasOrgs) Error() string {
|
||||||
return fmt.Sprintf("user still has membership of organizations: [uid: %d]", err.UID)
|
return fmt.Sprintf("user still has membership of organizations: [uid: %d]", err.UID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// __________ ___. .__ .__ ____ __.
|
||||||
|
// \______ \__ _\_ |__ | | |__| ____ | |/ _|____ ___.__.
|
||||||
|
// | ___/ | \ __ \| | | |/ ___\ | <_/ __ < | |
|
||||||
|
// | | | | / \_\ \ |_| \ \___ | | \ ___/\___ |
|
||||||
|
// |____| |____/|___ /____/__|\___ > |____|__ \___ > ____|
|
||||||
|
// \/ \/ \/ \/\/
|
||||||
|
|
||||||
|
type ErrKeyNotExist struct {
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrKeyNotExist(err error) bool {
|
||||||
|
_, ok := err.(ErrKeyNotExist)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrKeyNotExist) Error() string {
|
||||||
|
return fmt.Sprintf("public key does not exist: [id: %d]", err.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrKeyAlreadyExist struct {
|
||||||
|
OwnerID int64
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrKeyAlreadyExist(err error) bool {
|
||||||
|
_, ok := err.(ErrKeyAlreadyExist)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrKeyAlreadyExist) Error() string {
|
||||||
|
return fmt.Sprintf("public key already exists: [owner_id: %d, content: %s]", err.OwnerID, err.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrKeyNameAlreadyUsed struct {
|
||||||
|
OwnerID int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrKeyNameAlreadyUsed(err error) bool {
|
||||||
|
_, ok := err.(ErrKeyNameAlreadyUsed)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrKeyNameAlreadyUsed) Error() string {
|
||||||
|
return fmt.Sprintf("public key already exists: [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrDeployKeyAlreadyExist struct {
|
||||||
|
KeyID int64
|
||||||
|
RepoID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrDeployKeyAlreadyExist(err error) bool {
|
||||||
|
_, ok := err.(ErrDeployKeyAlreadyExist)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrDeployKeyAlreadyExist) Error() string {
|
||||||
|
return fmt.Sprintf("public key already exists: [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrDeployKeyNameAlreadyUsed struct {
|
||||||
|
RepoID int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrDeployKeyNameAlreadyUsed(err error) bool {
|
||||||
|
_, ok := err.(ErrDeployKeyNameAlreadyUsed)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrDeployKeyNameAlreadyUsed) Error() string {
|
||||||
|
return fmt.Sprintf("public key already exists: [repo_id: %d, name: %s]", err.RepoID, err.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// ________ .__ __ .__
|
// ________ .__ __ .__
|
||||||
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
|
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
|
||||||
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
|
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
|
||||||
|
|
|
@ -56,16 +56,11 @@ type Issue struct {
|
||||||
Updated time.Time `xorm:"UPDATED"`
|
Updated time.Time `xorm:"UPDATED"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Issue) BeforeSet(colName string, val xorm.Cell) {
|
func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
|
||||||
var err error
|
var err error
|
||||||
switch colName {
|
switch colName {
|
||||||
case "milestone_id":
|
case "milestone_id":
|
||||||
mid := (*val).(int64)
|
i.Milestone, err = GetMilestoneById(i.MilestoneID)
|
||||||
if mid <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
i.Milestone, err = GetMilestoneById(mid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(3, "GetMilestoneById: %v", err)
|
log.Error(3, "GetMilestoneById: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -664,15 +659,14 @@ type Milestone struct {
|
||||||
ClosedDate time.Time
|
ClosedDate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Milestone) BeforeSet(colName string, val xorm.Cell) {
|
func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
|
||||||
if colName == "deadline" {
|
if colName == "deadline" {
|
||||||
t := (*val).(time.Time)
|
if m.Deadline.Year() == 9999 {
|
||||||
if t.Year() == 9999 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.DeadlineString = t.Format("2006-01-02")
|
m.DeadlineString = m.Deadline.Format("2006-01-02")
|
||||||
if time.Now().After(t) {
|
if time.Now().After(m.Deadline) {
|
||||||
m.IsOverDue = true
|
m.IsOverDue = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ var migrations = []Migration{
|
||||||
NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3:v0.5.13
|
NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3:v0.5.13
|
||||||
NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4:v0.5.13
|
NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4:v0.5.13
|
||||||
NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
|
NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
|
||||||
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3 // V4 -> V5:v0.6.0
|
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate database to current version
|
// Migrate database to current version
|
||||||
|
|
|
@ -55,7 +55,7 @@ var (
|
||||||
func init() {
|
func init() {
|
||||||
tables = append(tables,
|
tables = append(tables,
|
||||||
new(User), new(PublicKey), new(Oauth2), new(AccessToken),
|
new(User), new(PublicKey), new(Oauth2), new(AccessToken),
|
||||||
new(Repository), new(Collaboration), new(Access),
|
new(Repository), new(DeployKey), new(Collaboration), new(Access),
|
||||||
new(Watch), new(Star), new(Follow), new(Action),
|
new(Watch), new(Star), new(Follow), new(Action),
|
||||||
new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
|
new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
|
||||||
new(Mirror), new(Release), new(LoginSource), new(Webhook),
|
new(Mirror), new(Release), new(LoginSource), new(Webhook),
|
||||||
|
@ -132,7 +132,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
|
||||||
func SetEngine() (err error) {
|
func SetEngine() (err error) {
|
||||||
x, err = getEngine()
|
x, err = getEngine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Connect to database: %v", err)
|
return fmt.Errorf("Fail to connect to database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
x.SetMapper(core.GonicMapper{})
|
x.SetMapper(core.GonicMapper{})
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Unknwon/com"
|
"github.com/Unknwon/com"
|
||||||
|
"github.com/go-xorm/xorm"
|
||||||
|
|
||||||
"github.com/gogits/gogs/modules/log"
|
"github.com/gogits/gogs/modules/log"
|
||||||
"github.com/gogits/gogs/modules/process"
|
"github.com/gogits/gogs/modules/process"
|
||||||
|
@ -33,8 +34,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrKeyAlreadyExist = errors.New("Public key already exists")
|
|
||||||
ErrKeyNotExist = errors.New("Public key does not exist")
|
|
||||||
ErrKeyUnableVerify = errors.New("Unable to verify public key")
|
ErrKeyUnableVerify = errors.New("Unable to verify public key")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -78,19 +77,36 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicKey represents a SSH key.
|
type KeyType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
KEY_TYPE_USER = iota + 1
|
||||||
|
KEY_TYPE_DEPLOY
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey represents a SSH or deploy key.
|
||||||
type PublicKey struct {
|
type PublicKey struct {
|
||||||
Id int64
|
ID int64 `xorm:"pk autoincr"`
|
||||||
OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
OwnerID int64 `xorm:"INDEX NOT NULL"`
|
||||||
Name string `xorm:"UNIQUE(s) NOT NULL"`
|
Name string `xorm:"NOT NULL"`
|
||||||
Fingerprint string `xorm:"INDEX NOT NULL"`
|
Fingerprint string `xorm:"NOT NULL"`
|
||||||
Content string `xorm:"TEXT NOT NULL"`
|
Content string `xorm:"UNIQUE TEXT NOT NULL"`
|
||||||
|
Mode AccessMode `xorm:"NOT NULL DEFAULT 2"`
|
||||||
|
Type KeyType `xorm:"NOT NULL DEFAULT 1"`
|
||||||
Created time.Time `xorm:"CREATED"`
|
Created time.Time `xorm:"CREATED"`
|
||||||
Updated time.Time
|
Updated time.Time // Note: Updated must below Created for AfterSet.
|
||||||
HasRecentActivity bool `xorm:"-"`
|
HasRecentActivity bool `xorm:"-"`
|
||||||
HasUsed bool `xorm:"-"`
|
HasUsed bool `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) {
|
||||||
|
switch colName {
|
||||||
|
case "created":
|
||||||
|
k.HasUsed = k.Updated.After(k.Created)
|
||||||
|
k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// OmitEmail returns content of public key but without e-mail address.
|
// OmitEmail returns content of public key but without e-mail address.
|
||||||
func (k *PublicKey) OmitEmail() string {
|
func (k *PublicKey) OmitEmail() string {
|
||||||
return strings.Join(strings.Split(k.Content, " ")[:2], " ")
|
return strings.Join(strings.Split(k.Content, " ")[:2], " ")
|
||||||
|
@ -98,7 +114,7 @@ func (k *PublicKey) OmitEmail() string {
|
||||||
|
|
||||||
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
|
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
|
||||||
func (key *PublicKey) GetAuthorizedString() string {
|
func (key *PublicKey) GetAuthorizedString() string {
|
||||||
return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.Id, setting.CustomConf, key.Content)
|
return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.ID, setting.CustomConf, key.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
var minimumKeySizes = map[string]int{
|
var minimumKeySizes = map[string]int{
|
||||||
|
@ -126,8 +142,8 @@ func extractTypeFromBase64Key(key string) (string, error) {
|
||||||
return string(b[4 : 4+keyLength]), nil
|
return string(b[4 : 4+keyLength]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse any key string in openssh or ssh2 format to clean openssh string (rfc4253)
|
// parseKeyString parses any key string in openssh or ssh2 format to clean openssh string (rfc4253)
|
||||||
func ParseKeyString(content string) (string, error) {
|
func parseKeyString(content string) (string, error) {
|
||||||
// Transform all legal line endings to a single "\n"
|
// Transform all legal line endings to a single "\n"
|
||||||
s := strings.Replace(strings.Replace(strings.TrimSpace(content), "\r\n", "\n", -1), "\r", "\n", -1)
|
s := strings.Replace(strings.Replace(strings.TrimSpace(content), "\r\n", "\n", -1), "\r", "\n", -1)
|
||||||
|
|
||||||
|
@ -190,16 +206,21 @@ func ParseKeyString(content string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
|
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
|
||||||
func CheckPublicKeyString(content string) (bool, error) {
|
func CheckPublicKeyString(content string) (_ string, err error) {
|
||||||
|
content, err = parseKeyString(content)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
content = strings.TrimRight(content, "\n\r")
|
content = strings.TrimRight(content, "\n\r")
|
||||||
if strings.ContainsAny(content, "\n\r") {
|
if strings.ContainsAny(content, "\n\r") {
|
||||||
return false, errors.New("only a single line with a single key please")
|
return "", errors.New("only a single line with a single key please")
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the key to a file…
|
// write the key to a file…
|
||||||
tmpFile, err := ioutil.TempFile(os.TempDir(), "keytest")
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "keytest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return "", err
|
||||||
}
|
}
|
||||||
tmpPath := tmpFile.Name()
|
tmpPath := tmpFile.Name()
|
||||||
defer os.Remove(tmpPath)
|
defer os.Remove(tmpPath)
|
||||||
|
@ -209,37 +230,36 @@ func CheckPublicKeyString(content string) (bool, error) {
|
||||||
// Check if ssh-keygen recognizes its contents.
|
// Check if ssh-keygen recognizes its contents.
|
||||||
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
|
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.New("ssh-keygen -l -f: " + stderr)
|
return "", errors.New("ssh-keygen -l -f: " + stderr)
|
||||||
} else if len(stdout) < 2 {
|
} else if len(stdout) < 2 {
|
||||||
return false, errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
|
return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ssh-keygen in Windows does not print key type, so no need go further.
|
// The ssh-keygen in Windows does not print key type, so no need go further.
|
||||||
if setting.IsWindows {
|
if setting.IsWindows {
|
||||||
return true, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(stdout)
|
|
||||||
sshKeygenOutput := strings.Split(stdout, " ")
|
sshKeygenOutput := strings.Split(stdout, " ")
|
||||||
if len(sshKeygenOutput) < 4 {
|
if len(sshKeygenOutput) < 4 {
|
||||||
return false, ErrKeyUnableVerify
|
return content, ErrKeyUnableVerify
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if key type and key size match.
|
// Check if key type and key size match.
|
||||||
if !setting.Service.DisableMinimumKeySizeCheck {
|
if !setting.Service.DisableMinimumKeySizeCheck {
|
||||||
keySize := com.StrTo(sshKeygenOutput[0]).MustInt()
|
keySize := com.StrTo(sshKeygenOutput[0]).MustInt()
|
||||||
if keySize == 0 {
|
if keySize == 0 {
|
||||||
return false, errors.New("cannot get key size of the given key")
|
return "", errors.New("cannot get key size of the given key")
|
||||||
}
|
}
|
||||||
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
|
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
|
||||||
if minimumKeySize := minimumKeySizes[keyType]; minimumKeySize == 0 {
|
if minimumKeySize := minimumKeySizes[keyType]; minimumKeySize == 0 {
|
||||||
return false, errors.New("sorry, unrecognized public key type")
|
return "", errors.New("sorry, unrecognized public key type")
|
||||||
} else if keySize < minimumKeySize {
|
} else if keySize < minimumKeySize {
|
||||||
return false, fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
|
return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
|
// saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
|
||||||
|
@ -278,20 +298,23 @@ func saveAuthorizedKeyFile(keys ...*PublicKey) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPublicKey adds new public key to database and authorized_keys file.
|
func checkKeyContent(content string) error {
|
||||||
func AddPublicKey(key *PublicKey) (err error) {
|
// Same key can only be added once.
|
||||||
has, err := x.Get(key)
|
has, err := x.Where("content=?", content).Get(new(PublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if has {
|
} else if has {
|
||||||
return ErrKeyAlreadyExist
|
return ErrKeyAlreadyExist{0, content}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addKey(e Engine, key *PublicKey) (err error) {
|
||||||
// Calculate fingerprint.
|
// Calculate fingerprint.
|
||||||
tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
|
tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
|
||||||
"id_rsa.pub"), "\\", "/", -1)
|
"id_rsa.pub"), "\\", "/", -1)
|
||||||
os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
|
os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
|
||||||
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil {
|
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
|
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
|
||||||
|
@ -301,32 +324,56 @@ func AddPublicKey(key *PublicKey) (err error) {
|
||||||
return errors.New("not enough output for calculating fingerprint: " + stdout)
|
return errors.New("not enough output for calculating fingerprint: " + stdout)
|
||||||
}
|
}
|
||||||
key.Fingerprint = strings.Split(stdout, " ")[1]
|
key.Fingerprint = strings.Split(stdout, " ")[1]
|
||||||
if has, err := x.Get(&PublicKey{Fingerprint: key.Fingerprint}); err == nil && has {
|
|
||||||
return ErrKeyAlreadyExist
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save SSH key.
|
// Save SSH key.
|
||||||
if _, err = x.Insert(key); err != nil {
|
if _, err = e.Insert(key); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = saveAuthorizedKeyFile(key); err != nil {
|
|
||||||
// Roll back.
|
|
||||||
if _, err2 := x.Delete(key); err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
}
|
||||||
|
return saveAuthorizedKeyFile(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPublicKey adds new public key to database and authorized_keys file.
|
||||||
|
func AddPublicKey(ownerID int64, name, content string) (err error) {
|
||||||
|
if err = checkKeyContent(content); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// Key name of same user cannot be duplicated.
|
||||||
|
has, err := x.Where("owner_id=? AND name=?", ownerID, name).Get(new(PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
return ErrKeyNameAlreadyUsed{ownerID, name}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicKeyById returns public key by given ID.
|
sess := x.NewSession()
|
||||||
func GetPublicKeyById(keyId int64) (*PublicKey, error) {
|
defer sessionRelease(sess)
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := &PublicKey{
|
||||||
|
OwnerID: ownerID,
|
||||||
|
Name: name,
|
||||||
|
Content: content,
|
||||||
|
Mode: ACCESS_MODE_WRITE,
|
||||||
|
Type: KEY_TYPE_USER,
|
||||||
|
}
|
||||||
|
if err = addKey(sess, key); err != nil {
|
||||||
|
return fmt.Errorf("addKey: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicKeyByID returns public key by given ID.
|
||||||
|
func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
|
||||||
key := new(PublicKey)
|
key := new(PublicKey)
|
||||||
has, err := x.Id(keyId).Get(key)
|
has, err := x.Id(keyID).Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, ErrKeyNotExist
|
return nil, ErrKeyNotExist{keyID}
|
||||||
}
|
}
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
@ -334,16 +381,7 @@ func GetPublicKeyById(keyId int64) (*PublicKey, error) {
|
||||||
// ListPublicKeys returns a list of public keys belongs to given user.
|
// ListPublicKeys returns a list of public keys belongs to given user.
|
||||||
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
|
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
|
||||||
keys := make([]*PublicKey, 0, 5)
|
keys := make([]*PublicKey, 0, 5)
|
||||||
err := x.Where("owner_id=?", uid).Find(&keys)
|
return keys, x.Where("owner_id=?", uid).Find(&keys)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, key := range keys {
|
|
||||||
key.HasUsed = key.Updated.After(key.Created)
|
|
||||||
key.HasRecentActivity = key.Updated.Add(7 * 24 * time.Hour).After(time.Now())
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
|
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
|
||||||
|
@ -364,7 +402,7 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||||
defer fw.Close()
|
defer fw.Close()
|
||||||
|
|
||||||
isFound := false
|
isFound := false
|
||||||
keyword := fmt.Sprintf("key-%d", key.Id)
|
keyword := fmt.Sprintf("key-%d", key.ID)
|
||||||
buf := bufio.NewReader(fr)
|
buf := bufio.NewReader(fr)
|
||||||
for {
|
for {
|
||||||
line, errRead := buf.ReadString('\n')
|
line, errRead := buf.ReadString('\n')
|
||||||
|
@ -401,20 +439,19 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||||
|
|
||||||
// UpdatePublicKey updates given public key.
|
// UpdatePublicKey updates given public key.
|
||||||
func UpdatePublicKey(key *PublicKey) error {
|
func UpdatePublicKey(key *PublicKey) error {
|
||||||
_, err := x.Id(key.Id).AllCols().Update(key)
|
_, err := x.Id(key.ID).AllCols().Update(key)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
|
func deletePublicKey(e *xorm.Session, key *PublicKey) error {
|
||||||
func DeletePublicKey(key *PublicKey) error {
|
has, err := e.Get(key)
|
||||||
has, err := x.Get(key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return ErrKeyNotExist
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = x.Delete(key); err != nil {
|
if _, err = e.Id(key.ID).Delete(key); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,6 +465,21 @@ func DeletePublicKey(key *PublicKey) error {
|
||||||
return os.Rename(tmpPath, fpath)
|
return os.Rename(tmpPath, fpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
|
||||||
|
func DeletePublicKey(key *PublicKey) (err error) {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sessionRelease(sess)
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = deletePublicKey(sess, key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
|
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
|
||||||
func RewriteAllPublicKeys() error {
|
func RewriteAllPublicKeys() error {
|
||||||
sshOpLocker.Lock()
|
sshOpLocker.Lock()
|
||||||
|
@ -461,3 +513,162 @@ func RewriteAllPublicKeys() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ________ .__ ____ __.
|
||||||
|
// \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
|
||||||
|
// | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
|
||||||
|
// | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
|
||||||
|
// /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
|
||||||
|
// \/ \/|__| \/ \/ \/\/
|
||||||
|
|
||||||
|
// DeployKey represents deploy key information and its relation with repository.
|
||||||
|
type DeployKey struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
KeyID int64 `xorm:"UNIQUE(s) INDEX"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s) INDEX"`
|
||||||
|
Name string
|
||||||
|
Fingerprint string
|
||||||
|
Created time.Time `xorm:"CREATED"`
|
||||||
|
Updated time.Time // Note: Updated must below Created for AfterSet.
|
||||||
|
HasRecentActivity bool `xorm:"-"`
|
||||||
|
HasUsed bool `xorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
|
||||||
|
switch colName {
|
||||||
|
case "created":
|
||||||
|
k.HasUsed = k.Updated.After(k.Created)
|
||||||
|
k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
|
||||||
|
// Note: We want error detail, not just true or false here.
|
||||||
|
has, err := e.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
return ErrDeployKeyAlreadyExist{keyID, repoID}
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err = e.Where("repo_id=? AND name=?", repoID, name).Get(new(DeployKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
return ErrDeployKeyNameAlreadyUsed{repoID, name}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addDeployKey adds new key-repo relation.
|
||||||
|
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (err error) {
|
||||||
|
if err = checkDeployKey(e, keyID, repoID, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = e.Insert(&DeployKey{
|
||||||
|
KeyID: keyID,
|
||||||
|
RepoID: repoID,
|
||||||
|
Name: name,
|
||||||
|
Fingerprint: fingerprint,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasDeployKey returns true if public key is a deploy key of given repository.
|
||||||
|
func HasDeployKey(keyID, repoID int64) bool {
|
||||||
|
has, _ := x.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDeployKey add new deploy key to database and authorized_keys file.
|
||||||
|
func AddDeployKey(repoID int64, name, content string) (err error) {
|
||||||
|
if err = checkKeyContent(content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := &PublicKey{
|
||||||
|
Content: content,
|
||||||
|
Mode: ACCESS_MODE_READ,
|
||||||
|
Type: KEY_TYPE_DEPLOY,
|
||||||
|
}
|
||||||
|
has, err := x.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sessionRelease(sess)
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// First time use this deploy key.
|
||||||
|
if !has {
|
||||||
|
if err = addKey(sess, key); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = addDeployKey(sess, key.ID, repoID, name, key.Fingerprint); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
|
||||||
|
func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
|
||||||
|
key := &DeployKey{
|
||||||
|
KeyID: keyID,
|
||||||
|
RepoID: repoID,
|
||||||
|
}
|
||||||
|
_, err := x.Get(key)
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDeployKey updates deploy key information.
|
||||||
|
func UpdateDeployKey(key *DeployKey) error {
|
||||||
|
_, err := x.Id(key.ID).AllCols().Update(key)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
|
||||||
|
func DeleteDeployKey(id int64) error {
|
||||||
|
key := &DeployKey{ID: id}
|
||||||
|
has, err := x.Id(key.ID).Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !has {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sessionRelease(sess)
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Id(key.ID).Delete(key); err != nil {
|
||||||
|
return fmt.Errorf("delete deploy key[%d]: %v", key.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the last reference to same key content.
|
||||||
|
has, err = sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !has {
|
||||||
|
if err = deletePublicKey(sess, &PublicKey{ID: key.KeyID}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDeployKeys returns all deploy keys by given repository ID.
|
||||||
|
func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
|
||||||
|
keys := make([]*DeployKey, 0, 5)
|
||||||
|
return keys, x.Where("repo_id=?", repoID).Find(&keys)
|
||||||
|
}
|
||||||
|
|
|
@ -505,7 +505,7 @@ func DeleteUser(u *User) error {
|
||||||
|
|
||||||
// Delete all SSH keys.
|
// Delete all SSH keys.
|
||||||
keys := make([]*PublicKey, 0, 10)
|
keys := make([]*PublicKey, 0, 10)
|
||||||
if err = sess.Find(&keys, &PublicKey{OwnerId: u.Id}); err != nil {
|
if err = sess.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
|
|
@ -126,8 +126,8 @@ func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddSSHKeyForm struct {
|
type AddSSHKeyForm struct {
|
||||||
SSHTitle string `form:"title" binding:"Required"`
|
Title string `binding:"Required;MaxSize(50)"`
|
||||||
Content string `form:"content" binding:"Required"`
|
Content string `binding:"Required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||||
|
|
File diff suppressed because one or more lines are too long
2
public/css/gogs.min.css
vendored
2
public/css/gogs.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -86,6 +86,13 @@ function initRepository() {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
if ($('.repository.settings').length > 0) {
|
||||||
|
$('#add-deploy-key').click(function () {
|
||||||
|
$('#add-deploy-key-panel').show();
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
|
@ -6,3 +6,9 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ui.attached.header {
|
||||||
|
background: #f0f0f0;
|
||||||
|
.right {
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
.install {
|
.install {
|
||||||
padding-top: 45px;
|
padding-top: 45px;
|
||||||
padding-bottom: @footer-margin * 3;
|
padding-bottom: @footer-margin * 3;
|
||||||
.attached.header {
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
form {
|
form {
|
||||||
label {
|
label {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
|
@ -217,6 +217,36 @@
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.settings {
|
||||||
|
.content {
|
||||||
|
padding-left: 20px!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings .key.list {
|
||||||
|
.item:not(:first-child) {
|
||||||
|
border-top: 1px solid #eaeaea;
|
||||||
|
}
|
||||||
|
.ssh-key-state-indicator {
|
||||||
|
float: left;
|
||||||
|
color: gray;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
&.active {
|
||||||
|
color: #6cc644;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
.print {
|
||||||
|
color: #767676;
|
||||||
|
}
|
||||||
|
.activity {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-label.modal {
|
.edit-label.modal {
|
||||||
|
|
|
@ -27,10 +27,11 @@ const (
|
||||||
SETTINGS_OPTIONS base.TplName = "repo/settings/options"
|
SETTINGS_OPTIONS base.TplName = "repo/settings/options"
|
||||||
COLLABORATION base.TplName = "repo/settings/collaboration"
|
COLLABORATION base.TplName = "repo/settings/collaboration"
|
||||||
HOOKS base.TplName = "repo/settings/hooks"
|
HOOKS base.TplName = "repo/settings/hooks"
|
||||||
GITHOOKS base.TplName = "repo/settings/githooks"
|
|
||||||
GITHOOK_EDIT base.TplName = "repo/settings/githook_edit"
|
|
||||||
HOOK_NEW base.TplName = "repo/settings/hook_new"
|
HOOK_NEW base.TplName = "repo/settings/hook_new"
|
||||||
ORG_HOOK_NEW base.TplName = "org/settings/hook_new"
|
ORG_HOOK_NEW base.TplName = "org/settings/hook_new"
|
||||||
|
GITHOOKS base.TplName = "repo/settings/githooks"
|
||||||
|
GITHOOK_EDIT base.TplName = "repo/settings/githook_edit"
|
||||||
|
DEPLOY_KEYS base.TplName = "repo/settings/deploy_keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Settings(ctx *middleware.Context) {
|
func Settings(ctx *middleware.Context) {
|
||||||
|
@ -584,6 +585,10 @@ func getOrgRepoCtx(ctx *middleware.Context) (*OrgRepoCtx, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TriggerHook(ctx *middleware.Context) {
|
||||||
|
models.HookQueue.AddRepoID(ctx.Repo.Repository.Id)
|
||||||
|
}
|
||||||
|
|
||||||
func GitHooks(ctx *middleware.Context) {
|
func GitHooks(ctx *middleware.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
||||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||||
|
@ -635,6 +640,70 @@ func GitHooksEditPost(ctx *middleware.Context) {
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git")
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TriggerHook(ctx *middleware.Context) {
|
func SettingsDeployKeys(ctx *middleware.Context) {
|
||||||
models.HookQueue.AddRepoID(ctx.Repo.Repository.Id)
|
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
||||||
|
ctx.Data["PageIsSettingsKeys"] = true
|
||||||
|
|
||||||
|
keys, err := models.ListDeployKeys(ctx.Repo.Repository.Id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "ListDeployKeys", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["Deploykeys"] = keys
|
||||||
|
|
||||||
|
ctx.HTML(200, DEPLOY_KEYS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SettingsDeployKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
||||||
|
ctx.Data["PageIsSettingsKeys"] = true
|
||||||
|
|
||||||
|
if ctx.HasError() {
|
||||||
|
ctx.HTML(200, DEPLOY_KEYS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := models.CheckPublicKeyString(form.Content)
|
||||||
|
if err != nil {
|
||||||
|
if err == models.ErrKeyUnableVerify {
|
||||||
|
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
||||||
|
} else {
|
||||||
|
ctx.Data["HasError"] = true
|
||||||
|
ctx.Data["Err_Content"] = true
|
||||||
|
ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = models.AddDeployKey(ctx.Repo.Repository.Id, form.Title, content); err != nil {
|
||||||
|
ctx.Data["HasError"] = true
|
||||||
|
switch {
|
||||||
|
case models.IsErrKeyAlreadyExist(err):
|
||||||
|
ctx.Data["Err_Content"] = true
|
||||||
|
ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), DEPLOY_KEYS, &form)
|
||||||
|
case models.IsErrKeyNameAlreadyUsed(err):
|
||||||
|
ctx.Data["Err_Title"] = true
|
||||||
|
ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), DEPLOY_KEYS, &form)
|
||||||
|
default:
|
||||||
|
ctx.Handle(500, "AddDeployKey", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("Deploy key added: %d", ctx.Repo.Repository.Id)
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", form.Title))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteDeployKey(ctx *middleware.Context) {
|
||||||
|
if err := models.DeleteDeployKey(ctx.QueryInt64("id")); err != nil {
|
||||||
|
ctx.Flash.Error("DeleteDeployKey: " + err.Error())
|
||||||
|
} else {
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(200, map[string]interface{}{
|
||||||
|
"redirect": ctx.Repo.RepoLink + "/settings/keys",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,7 +305,7 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = models.DeletePublicKey(&models.PublicKey{Id: id}); err != nil {
|
if err = models.DeletePublicKey(&models.PublicKey{ID: id}); err != nil {
|
||||||
ctx.Handle(500, "DeletePublicKey", err)
|
ctx.Handle(500, "DeletePublicKey", err)
|
||||||
} else {
|
} else {
|
||||||
log.Trace("SSH key deleted: %s", ctx.User.Name)
|
log.Trace("SSH key deleted: %s", ctx.User.Name)
|
||||||
|
@ -321,15 +321,8 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse openssh style string from form content
|
content, err := models.CheckPublicKeyString(form.Content)
|
||||||
content, err := models.ParseKeyString(form.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
|
|
||||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := models.CheckPublicKeyString(content); !ok {
|
|
||||||
if err == models.ErrKeyUnableVerify {
|
if err == models.ErrKeyUnableVerify {
|
||||||
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
||||||
} else {
|
} else {
|
||||||
|
@ -339,21 +332,19 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
k := &models.PublicKey{
|
if err = models.AddPublicKey(ctx.User.Id, form.Title, content); err != nil {
|
||||||
OwnerId: ctx.User.Id,
|
switch {
|
||||||
Name: form.SSHTitle,
|
case models.IsErrKeyAlreadyExist(err):
|
||||||
Content: content,
|
ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), SETTINGS_SSH_KEYS, &form)
|
||||||
|
case models.IsErrKeyNameAlreadyUsed(err):
|
||||||
|
ctx.RenderWithErr(ctx.Tr("settings.ssh_key_name_used"), SETTINGS_SSH_KEYS, &form)
|
||||||
|
default:
|
||||||
|
ctx.Handle(500, "AddPublicKey", err)
|
||||||
}
|
}
|
||||||
if err := models.AddPublicKey(k); err != nil {
|
|
||||||
if err == models.ErrKeyAlreadyExist {
|
|
||||||
ctx.RenderWithErr(ctx.Tr("form.ssh_key_been_used"), SETTINGS_SSH_KEYS, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Handle(500, "ssh.AddPublicKey", err)
|
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
log.Trace("SSH key added: %s", ctx.User.Name)
|
log.Trace("SSH key added: %s", ctx.User.Name)
|
||||||
ctx.Flash.Success(ctx.Tr("settings.add_key_success"))
|
ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
|
||||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.6.4.0805 Beta
|
0.6.4.0806 Beta
|
|
@ -8,3 +8,8 @@
|
||||||
<p>{{.Flash.SuccessMsg}}</p>
|
<p>{{.Flash.SuccessMsg}}</p>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if .Flash.InfoMsg}}
|
||||||
|
<div class="ui info message">
|
||||||
|
<p>{{.Flash.InfoMsg}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
97
templates/repo/settings/deploy_keys.tmpl
Normal file
97
templates/repo/settings/deploy_keys.tmpl
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
<div class="repository settings">
|
||||||
|
{{template "repo/header" .}}
|
||||||
|
<div class="ui page grid">
|
||||||
|
{{template "repo/settings/navbar" .}}
|
||||||
|
<div class="twelve wide column content">
|
||||||
|
{{template "base/alert" .}}
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{.i18n.Tr "repo.settings.deploy_keys"}}
|
||||||
|
<div class="ui right">
|
||||||
|
<div id="add-deploy-key" class="ui blue tiny button">{{.i18n.Tr "repo.settings.add_deploy_key"}}</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
{{if .Deploykeys}}
|
||||||
|
<div class="ui key list">
|
||||||
|
{{range .Deploykeys}}
|
||||||
|
<div class="item ui grid">
|
||||||
|
<div class="one wide column">
|
||||||
|
<i class="ssh-key-state-indicator fa fa-circle{{if .HasRecentActivity}} active invert poping up{{else}}-o{{end}}" {{if .HasRecentActivity}}data-content="{{$.i18n.Tr "settings.key_state_desc"}}" data-variation="inverted"{{end}}></i>
|
||||||
|
</div>
|
||||||
|
<div class="one wide column">
|
||||||
|
<i class="mega-octicon octicon-key left"></i>
|
||||||
|
</div>
|
||||||
|
<div class="eleven wide column">
|
||||||
|
<strong>{{.Name}}</strong>
|
||||||
|
<div class="print meta">
|
||||||
|
{{.Fingerprint}}
|
||||||
|
</div>
|
||||||
|
<div class="activity meta">
|
||||||
|
<i>{{$.i18n.Tr "settings.add_on"}} <span>{{DateFmtShort .Created}}</span> — <i class="octicon octicon-info"></i> {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span>{{DateFmtShort .Updated}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="three wide column">
|
||||||
|
<button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
|
||||||
|
{{$.i18n.Tr "settings.delete_key"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{.i18n.Tr "repo.settings.no_deploy_keys"}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div {{if not .HasError}}class="hide"{{end}} id="add-deploy-key-panel">
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{.i18n.Tr "repo.settings.add_deploy_key"}}
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<div class="field {{if .Err_Title}}error{{end}}">
|
||||||
|
<label>{{.i18n.Tr "repo.settings.title"}}</label>
|
||||||
|
<input name="title" value="{{.title}}" autofocus required>
|
||||||
|
</div>
|
||||||
|
<div class="field {{if .Err_Content}}error{{end}}">
|
||||||
|
<label>{{.i18n.Tr "repo.settings.deploy_key_content"}}</label>
|
||||||
|
<textarea name="content" required>{{.content}}</textarea>
|
||||||
|
</div>
|
||||||
|
<button class="ui green button">
|
||||||
|
{{.i18n.Tr "repo.settings.add_deploy_key"}}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui basic delete modal">
|
||||||
|
<div class="header">
|
||||||
|
{{.i18n.Tr "repo.settings.deploy_key_deletion"}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="image">
|
||||||
|
<i class="trash icon"></i>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>{{.i18n.Tr "repo.settings.deploy_key_deletion_desc"}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="two fluid ui inverted buttons">
|
||||||
|
<div class="ui red basic inverted button">
|
||||||
|
<i class="remove icon"></i>
|
||||||
|
{{.i18n.Tr "modal.no"}}
|
||||||
|
</div>
|
||||||
|
<div class="ui green basic inverted positive button">
|
||||||
|
<i class="checkmark icon"></i>
|
||||||
|
{{.i18n.Tr "modal.yes"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
|
@ -8,7 +8,7 @@
|
||||||
{{if or .SignedUser.AllowGitHook .SignedUser.IsAdmin}}
|
{{if or .SignedUser.AllowGitHook .SignedUser.IsAdmin}}
|
||||||
<li {{if .PageIsSettingsGitHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks/git">{{.i18n.Tr "repo.settings.githooks"}}</a></li>
|
<li {{if .PageIsSettingsGitHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks/git">{{.i18n.Tr "repo.settings.githooks"}}</a></li>
|
||||||
{{end}}
|
{{end}}
|
||||||
<!-- <li {{if .PageIsSettingsKeys}}class="current"{{end}}><a href="{{.RepoLink}}/settings/keys">{{.i18n.Tr "repo.settings.deploy_keys"}}</a></li> -->
|
<li {{if .PageIsSettingsKeys}}class="current"{{end}}><a href="{{.RepoLink}}/settings/keys">{{.i18n.Tr "repo.settings.deploy_keys"}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
19
templates/repo/settings/navbar.tmpl
Normal file
19
templates/repo/settings/navbar.tmpl
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<div class="four wide column">
|
||||||
|
<div class="ui vertical menu">
|
||||||
|
<a class="{{if .PageIsSettingsOptions}}active{{end}} item" href="{{.RepoLink}}/settings">
|
||||||
|
{{.i18n.Tr "repo.settings.options"}}
|
||||||
|
</a>
|
||||||
|
<a class="{{if .PageIsSettingsCollaboration}}active{{end}} item" href="{.RepoLink}}/settings/collaboration">
|
||||||
|
{{.i18n.Tr "repo.settings.collaboration"}}
|
||||||
|
</a>
|
||||||
|
<a class="{{if .PageIsSettingsHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks">
|
||||||
|
{{.i18n.Tr "repo.settings.hooks"}}
|
||||||
|
</a>
|
||||||
|
<a class="{{if .PageIsSettingsGitHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks/git">
|
||||||
|
{{.i18n.Tr "repo.settings.githooks"}}
|
||||||
|
</a>
|
||||||
|
<a class="{{if .PageIsSettingsKeys}}active{{end}} item" href="{{.RepoLink}}/settings/keys">
|
||||||
|
{{.i18n.Tr "repo.settings.deploy_keys"}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -28,7 +28,7 @@
|
||||||
<form action="{{AppSubUrl}}/user/settings/ssh" method="post">
|
<form action="{{AppSubUrl}}/user/settings/ssh" method="post">
|
||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<input name="_method" type="hidden" value="DELETE">
|
<input name="_method" type="hidden" value="DELETE">
|
||||||
<input name="id" type="hidden" value="{{.Id}}">
|
<input name="id" type="hidden" value="{{.ID}}">
|
||||||
<button class="right ssh-btn btn btn-red btn-radius btn-small">{{$.i18n.Tr "settings.delete_key"}}</button>
|
<button class="right ssh-btn btn btn-red btn-radius btn-small">{{$.i18n.Tr "settings.delete_key"}}</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in a new issue