Merge branch 'master' into mark-as-unread

Этот коммит содержится в:
Harrison Healey
2019-11-19 09:45:03 -05:00
родитель 2f066da704 c40f0a4aea
Коммит de913e7537
108 изменённых файлов: 11124 добавлений и 1190 удалений

Просмотреть файл

@@ -63,6 +63,8 @@ jobs:
at: /go/src/github.com/mattermost/ at: /go/src/github.com/mattermost/
- run: - run:
command: | command: |
echo "Installing golangci-lint"
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b /usr/local/bin v1.21.0
cd mattermost-server cd mattermost-server
make config-reset make config-reset
make check-style BUILD_NUMBER='${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}' make check-style BUILD_NUMBER='${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}'

Просмотреть файл

@@ -46,7 +46,6 @@ else
endif endif
# Golang Flags # Golang Flags
export GO111MODULE=on
GOPATH ?= $(shell go env GOPATH) GOPATH ?= $(shell go env GOPATH)
GOFLAGS ?= $(GOFLAGS:) -mod=vendor GOFLAGS ?= $(GOFLAGS:) -mod=vendor
GO=go GO=go
@@ -149,31 +148,10 @@ else
endif endif
govet: ## Runs govet against all packages. plugin-checker:
@echo Running GOVET
env GO111MODULE=off $(GO) get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
$(GO) vet $(GOFLAGS) $(ALL_PACKAGES) || exit 1
$(GO) vet -vettool=$(GOPATH)/bin/shadow $(GOFLAGS) $(ALL_PACKAGES) || exit 1
$(GO) run $(GOFLAGS) ./plugin/checker $(GO) run $(GOFLAGS) ./plugin/checker
gofmt: ## Runs gofmt against all packages. golangci-lint: ## Run golangci-lint on codebase
@echo Running GOFMT
@for package in $(TE_PACKAGES) $(EE_PACKAGES); do \
echo "Checking "$$package; \
files=$$($(GO) list $(GOFLAGS) -f '{{range .GoFiles}}{{$$.Dir}}/{{.}} {{end}}' $$package); \
if [ "$$files" ]; then \
gofmt_output=$$(gofmt -d -s $$files 2>&1); \
if [ "$$gofmt_output" ]; then \
echo "$$gofmt_output"; \
echo "gofmt failure"; \
exit 1; \
fi; \
fi; \
done
@echo "gofmt success"; \
golangci-lint: ## Run golangci-lint on codebasis
# https://stackoverflow.com/a/677212/1027058 (check if a command exists or not) # https://stackoverflow.com/a/677212/1027058 (check if a command exists or not)
@if ! [ -x "$$(command -v golangci-lint)" ]; then \ @if ! [ -x "$$(command -v golangci-lint)" ]; then \
echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \ echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \
@@ -229,8 +207,7 @@ check-licenses: ## Checks license status.
check-prereqs: ## Checks prerequisite software status. check-prereqs: ## Checks prerequisite software status.
./scripts/prereq-check.sh ./scripts/prereq-check.sh
# TODO: remove govet and gofmt checks once golangci-lint is being enforced. check-style: golangci-lint plugin-checker check-licenses check-plugin-golint ## Runs golangci against all packages and also ensures plugin package golint compliant
check-style: govet gofmt check-licenses check-plugin-golint ## Runs govet and gofmt against all packages and also ensures plugin package golint compliant
check-plugin-golint: # Checks if golint returns any uncompliant code for any file that starts with plugin/helpers check-plugin-golint: # Checks if golint returns any uncompliant code for any file that starts with plugin/helpers
@! golint ./plugin/ | grep plugin/helpers @! golint ./plugin/ | grep plugin/helpers

Просмотреть файл

@@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
) )
func TestEchoCommand(t *testing.T) { func TestEchoCommand(t *testing.T) {
@@ -20,20 +21,16 @@ func TestEchoCommand(t *testing.T) {
echoTestString := "/echo test" echoTestString := "/echo test"
if r1 := Client.Must(Client.ExecuteCommand(channel1.Id, echoTestString)).(*model.CommandResponse); r1 == nil { r1 := Client.Must(Client.ExecuteCommand(channel1.Id, echoTestString)).(*model.CommandResponse)
t.Fatal("Echo command failed to execute") require.NotNil(t, r1, "Echo command failed to execute")
}
if r1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/echo ")).(*model.CommandResponse); r1 == nil { r1 = Client.Must(Client.ExecuteCommand(channel1.Id, "/echo ")).(*model.CommandResponse)
t.Fatal("Echo command failed to execute") require.NotNil(t, r1, "Echo command failed to execute")
}
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPostsForChannel(channel1.Id, 0, 2, "")).(*model.PostList) p1 := Client.Must(Client.GetPostsForChannel(channel1.Id, 0, 2, "")).(*model.PostList)
if len(p1.Order) != 2 { require.Len(t, p1.Order, 2, "Echo command failed to send")
t.Fatal("Echo command failed to send")
}
} }
func TestGroupmsgCommands(t *testing.T) { func TestGroupmsgCommands(t *testing.T) {
@@ -57,25 +54,18 @@ func TestGroupmsgCommands(t *testing.T) {
rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse) rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse)
group1 := model.GetGroupNameFromUserIds([]string{user1.Id, user2.Id, user3.Id}) group1 := model.GetGroupNameFromUserIds([]string{user1.Id, user2.Id, user3.Id})
require.True(t, strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+group1), "failed to create group channel")
if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+group1) {
t.Fatal("failed to create group channel")
}
rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user3.Username+","+user4.Username+" foobar")).(*model.CommandResponse) rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user3.Username+","+user4.Username+" foobar")).(*model.CommandResponse)
group2 := model.GetGroupNameFromUserIds([]string{user1.Id, user3.Id, user4.Id}) group2 := model.GetGroupNameFromUserIds([]string{user1.Id, user3.Id, user4.Id})
if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+group2) { require.True(t, strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+group2), "failed to create second direct channel")
t.Fatal("failed to create second direct channel")
} result := Client.Must(Client.SearchPosts(team.Id, "foobar", false)).(*model.PostList)
if result := Client.Must(Client.SearchPosts(team.Id, "foobar", false)).(*model.PostList); len(result.Order) == 0 { require.NotEqual(t, 0, len(result.Order), "post did not get sent to direct message")
t.Fatal("post did not get sent to direct message")
}
rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse) rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse)
if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+group1) { require.True(t, strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+group1), "failed to go back to existing group channel")
t.Fatal("failed to go back to existing group channel")
}
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+" foobar")) Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+" foobar"))
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username+","+user4.Username+","+user5.Username+","+user6.Username+","+user7.Username+","+user8.Username+","+user9.Username+" foobar")) Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username+","+user4.Username+","+user5.Username+","+user6.Username+","+user7.Username+","+user8.Username+","+user9.Username+" foobar"))
@@ -91,19 +81,13 @@ func TestInvitePeopleCommand(t *testing.T) {
channel := th.BasicChannel channel := th.BasicChannel
r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test@example.com")).(*model.CommandResponse) r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test@example.com")).(*model.CommandResponse)
if r1 == nil { require.NotNil(t, r1, "Command failed to execute")
t.Fatal("Command failed to execute")
}
r2 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test1@example.com test2@example.com")).(*model.CommandResponse) r2 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test1@example.com test2@example.com")).(*model.CommandResponse)
if r2 == nil { require.NotNil(t, r2, "Command failed to execute")
t.Fatal("Command failed to execute")
}
r3 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people")).(*model.CommandResponse) r3 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people")).(*model.CommandResponse)
if r3 == nil { require.NotNil(t, r3, "Command failed to execute")
t.Fatal("Command failed to execute")
}
} }
// also used to test /open (see command_open_test.go) // also used to test /open (see command_open_test.go)
@@ -129,14 +113,10 @@ func testJoinCommands(t *testing.T, alias string) {
channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel) channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel)
rs5 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel2.Name)).(*model.CommandResponse) rs5 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel2.Name)).(*model.CommandResponse)
if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { require.True(t, strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name), "failed to join channel")
t.Fatal("failed to join channel")
}
rs6 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel3.Name)).(*model.CommandResponse) rs6 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel3.Name)).(*model.CommandResponse)
if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { require.False(t, strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name), "should not have joined direct message channel")
t.Fatal("should not have joined direct message channel")
}
c1 := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel) c1 := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel)
@@ -146,10 +126,7 @@ func testJoinCommands(t *testing.T, alias string) {
found = true found = true
} }
} }
require.True(t, found, "did not join channel")
if !found {
t.Fatal("did not join channel")
}
} }
func TestJoinCommands(t *testing.T) { func TestJoinCommands(t *testing.T) {
@@ -171,9 +148,7 @@ func TestLoadTestHelpCommands(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true })
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test help")).(*model.CommandResponse) rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test help")).(*model.CommandResponse)
if !strings.Contains(rs.Text, "Mattermost testing commands to help") { require.True(t, strings.Contains(rs.Text, "Mattermost testing commands to help"), rs.Text)
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
} }
@@ -193,9 +168,7 @@ func TestLoadTestSetupCommands(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true })
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test setup fuzz 1 1 1")).(*model.CommandResponse) rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test setup fuzz 1 1 1")).(*model.CommandResponse)
if rs.Text != "Created environment" { require.Equal(t, "Created environment", rs.Text, rs.Text)
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
} }
@@ -215,9 +188,7 @@ func TestLoadTestUsersCommands(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true })
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test users fuzz 1 2")).(*model.CommandResponse) rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test users fuzz 1 2")).(*model.CommandResponse)
if rs.Text != "Added users" { require.Equal(t, "Added users", rs.Text, rs.Text)
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
} }
@@ -237,9 +208,7 @@ func TestLoadTestChannelsCommands(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true })
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test channels fuzz 1 2")).(*model.CommandResponse) rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test channels fuzz 1 2")).(*model.CommandResponse)
if rs.Text != "Added channels" { require.Equal(t, "Added channels", rs.Text, rs.Text)
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
} }
@@ -259,9 +228,7 @@ func TestLoadTestPostsCommands(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableTesting = true })
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test posts fuzz 2 3 2")).(*model.CommandResponse) rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test posts fuzz 2 3 2")).(*model.CommandResponse)
if rs.Text != "Added posts" { require.Equal(t, "Added posts", rs.Text, rs.Text)
t.Fatal(rs.Text)
}
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
} }
@@ -286,19 +253,13 @@ func TestLeaveCommands(t *testing.T) {
channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel) channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel)
rs1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/leave")).(*model.CommandResponse) rs1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/leave")).(*model.CommandResponse)
if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { require.True(t, strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL), "failed to leave open channel 1")
t.Fatal("failed to leave open channel 1")
}
rs2 := Client.Must(Client.ExecuteCommand(channel2.Id, "/leave")).(*model.CommandResponse) rs2 := Client.Must(Client.ExecuteCommand(channel2.Id, "/leave")).(*model.CommandResponse)
if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { require.True(t, strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL), "failed to leave private channel 1")
t.Fatal("failed to leave private channel 1")
}
_, err := Client.ExecuteCommand(channel3.Id, "/leave") _, err := Client.ExecuteCommand(channel3.Id, "/leave")
if err == nil { require.NotNil(t, err, "should fail leaving direct channel")
t.Fatal("should fail leaving direct channel")
}
cdata := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel) cdata := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel)
@@ -308,16 +269,12 @@ func TestLeaveCommands(t *testing.T) {
found = true found = true
} }
} }
require.False(t, found, "did not leave right channels")
if found {
t.Fatal("did not leave right channels")
}
for _, c := range cdata { for _, c := range cdata {
if c.Name == model.DEFAULT_CHANNEL { if c.Name == model.DEFAULT_CHANNEL {
if _, err := Client.RemoveUserFromChannel(c.Id, th.BasicUser.Id); err == nil { _, err := Client.RemoveUserFromChannel(c.Id, th.BasicUser.Id)
t.Fatal("should have errored on leaving default channel") require.NotNil(t, err, "should have errored on leaving default channel")
}
break break
} }
} }
@@ -340,28 +297,19 @@ func TestMeCommand(t *testing.T) {
testString := "/me hello" testString := "/me hello"
r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse) r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse)
if r1 == nil { require.NotNil(t, r1, "Command failed to execute")
t.Fatal("Command failed to execute")
}
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList) p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList)
if len(p1.Order) != 2 { require.Len(t, p1.Order, 2, "Command failed to send")
t.Fatal("Command failed to send")
} else {
pt := p1.Posts[p1.Order[0]].Type pt := p1.Posts[p1.Order[0]].Type
if pt != model.POST_ME { require.Equal(t, model.POST_ME, pt, "invalid post type")
t.Log(pt)
t.Fatalf("invalid post type, got '%s', wanted '%s'", pt, model.POST_ME)
}
msg := p1.Posts[p1.Order[0]].Message msg := p1.Posts[p1.Order[0]].Message
want := "*hello*" want := "*hello*"
if msg != want { require.Equal(t, want, msg, "invalid me response")
t.Log(msg)
t.Fatalf("invalid me response message, got '%s', wanted '%s'", msg, want)
}
}
} }
func TestMsgCommands(t *testing.T) { func TestMsgCommands(t *testing.T) {
@@ -379,22 +327,25 @@ func TestMsgCommands(t *testing.T) {
Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user3.Id)) Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user3.Id))
rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse) rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse)
if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) { require.Condition(t, func() bool {
t.Fatal("failed to create direct channel") return strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) ||
} strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id)
}, "failed to create direct channel")
rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user3.Username+" foobar")).(*model.CommandResponse) rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user3.Username+" foobar")).(*model.CommandResponse)
if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user3.Id) && !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user3.Id+"__"+user1.Id) { require.Condition(t, func() bool {
t.Fatal("failed to create second direct channel") return strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user3.Id) ||
} strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user3.Id+"__"+user1.Id)
if result := Client.Must(Client.SearchPosts(th.BasicTeam.Id, "foobar", false)).(*model.PostList); len(result.Order) == 0 { }, "failed to create second direct channel")
t.Fatalf("post did not get sent to direct message")
} result := Client.Must(Client.SearchPosts(th.BasicTeam.Id, "foobar", false)).(*model.PostList)
require.NotEqual(t, 0, len(result.Order), "post did not get sent to direct message")
rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse) rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse)
if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) { require.Condition(t, func() bool {
t.Fatal("failed to go back to existing direct channel") return strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) ||
} strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id)
}, "failed to go back to existing direct channel")
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+th.BasicUser.Username+" foobar")) Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+th.BasicUser.Username+" foobar"))
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg junk foobar")) Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg junk foobar"))
@@ -435,21 +386,13 @@ func TestShrugCommand(t *testing.T) {
testString := "/shrug" testString := "/shrug"
r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse) r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse)
if r1 == nil { require.NotNil(t, r1, "Command failed to execute")
t.Fatal("Command failed to execute")
}
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList) p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList)
if len(p1.Order) != 2 { require.Len(t, p1.Order, 2, "Command failed to send")
t.Fatal("Command failed to send") require.Equal(t, `¯\\\_(ツ)\_/¯`, p1.Posts[p1.Order[0]].Message, "invalid shrug response")
} else {
if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` {
t.Log(p1.Posts[p1.Order[0]].Message)
t.Fatal("invalid shrug response")
}
}
} }
func TestStatusCommands(t *testing.T) { func TestStatusCommands(t *testing.T) {
@@ -467,15 +410,10 @@ func commandAndTest(t *testing.T, th *TestHelper, status string) {
user := th.BasicUser user := th.BasicUser
r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/"+status)).(*model.CommandResponse) r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/"+status)).(*model.CommandResponse)
if r1 == nil { require.NotEqual(t, "Command failed to execute", r1)
t.Fatal("Command failed to execute")
}
time.Sleep(1000 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
rstatus := Client.Must(Client.GetUserStatus(user.Id, "")).(*model.Status) rstatus := Client.Must(Client.GetUserStatus(user.Id, "")).(*model.Status)
require.Equal(t, status, rstatus.Status, "Error setting status")
if rstatus.Status != status {
t.Fatal("Error setting status " + status)
}
} }

Просмотреть файл

@@ -575,23 +575,40 @@ func getGroups(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = model.NewAppError("Api4.getGroups", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented) c.Err = model.NewAppError("Api4.getGroups", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return return
} }
var teamID, channelID string
if id := c.Params.NotAssociatedToTeam; model.IsValidId(id) {
teamID = id
}
if id := c.Params.NotAssociatedToChannel; model.IsValidId(id) {
channelID = id
}
if teamID == "" && channelID == "" {
c.Err = model.NewAppError("Api4.getGroups", "api.getGroups.invalid_or_missing_channel_or_team_id", nil, "", http.StatusBadRequest)
return
}
opts := model.GroupSearchOpts{ opts := model.GroupSearchOpts{
Q: c.Params.Q, Q: c.Params.Q,
IncludeMemberCount: c.Params.IncludeMemberCount, IncludeMemberCount: c.Params.IncludeMemberCount,
} }
teamID := c.Params.NotAssociatedToTeam if teamID != "" {
if len(teamID) == 26 { _, err := c.App.GetTeam(teamID)
if !c.App.SessionHasPermissionToTeam(c.App.Session, teamID, model.PERMISSION_VIEW_TEAM) { if err != nil {
c.SetPermissionError(model.PERMISSION_VIEW_TEAM) c.Err = err
return
}
if !c.App.SessionHasPermissionToTeam(c.App.Session, teamID, model.PERMISSION_MANAGE_TEAM) {
c.SetPermissionError(model.PERMISSION_MANAGE_TEAM)
return return
} }
opts.NotAssociatedToTeam = teamID opts.NotAssociatedToTeam = teamID
} }
channelID := c.Params.NotAssociatedToChannel if channelID != "" {
if len(channelID) == 26 {
channel, err := c.App.GetChannel(channelID) channel, err := c.App.GetChannel(channelID)
if err != nil { if err != nil {
c.Err = err c.Err = err

Просмотреть файл

@@ -8,11 +8,9 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestGetGroup(t *testing.T) { func TestGetGroup(t *testing.T) {
@@ -765,6 +763,20 @@ func TestGetGroups(t *testing.T) {
th.App.SetLicense(model.NewTestLicense("ldap")) th.App.SetLicense(model.NewTestLicense("ldap"))
_, response = th.SystemAdminClient.GetGroups(opts)
CheckBadRequestStatus(t, response)
_, response = th.SystemAdminClient.UpdateChannelRoles(th.BasicChannel.Id, th.BasicUser.Id, "")
require.Nil(t, response.Error)
opts.NotAssociatedToChannel = th.BasicChannel.Id
_, response = th.Client.GetGroups(opts)
CheckForbiddenStatus(t, response)
_, response = th.SystemAdminClient.UpdateChannelRoles(th.BasicChannel.Id, th.BasicUser.Id, "channel_user channel_admin")
require.Nil(t, response.Error)
groups, response := th.SystemAdminClient.GetGroups(opts) groups, response := th.SystemAdminClient.GetGroups(opts)
assert.Nil(t, response.Error) assert.Nil(t, response.Error)
assert.ElementsMatch(t, []*model.Group{group, th.Group}, groups) assert.ElementsMatch(t, []*model.Group{group, th.Group}, groups)
@@ -787,7 +799,7 @@ func TestGetGroups(t *testing.T) {
_, response = th.Client.GetGroups(opts) _, response = th.Client.GetGroups(opts)
CheckForbiddenStatus(t, response) CheckForbiddenStatus(t, response)
_, response = th.SystemAdminClient.UpdateTeamMemberRoles(th.BasicTeam.Id, th.BasicUser.Id, "team_user") _, response = th.SystemAdminClient.UpdateTeamMemberRoles(th.BasicTeam.Id, th.BasicUser.Id, "team_user team_admin")
require.Nil(t, response.Error) require.Nil(t, response.Error)
_, response = th.Client.GetGroups(opts) _, response = th.Client.GetGroups(opts)

Просмотреть файл

@@ -8,6 +8,7 @@ package api4
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
@@ -31,6 +32,7 @@ func (api *API) InitPlugin() {
api.BaseRoutes.Plugins.Handle("", api.ApiSessionRequired(getPlugins)).Methods("GET") api.BaseRoutes.Plugins.Handle("", api.ApiSessionRequired(getPlugins)).Methods("GET")
api.BaseRoutes.Plugin.Handle("", api.ApiSessionRequired(removePlugin)).Methods("DELETE") api.BaseRoutes.Plugin.Handle("", api.ApiSessionRequired(removePlugin)).Methods("DELETE")
api.BaseRoutes.Plugins.Handle("/install_from_url", api.ApiSessionRequired(installPluginFromUrl)).Methods("POST") api.BaseRoutes.Plugins.Handle("/install_from_url", api.ApiSessionRequired(installPluginFromUrl)).Methods("POST")
api.BaseRoutes.Plugins.Handle("/marketplace", api.ApiSessionRequired(installMarketplacePlugin)).Methods("POST")
api.BaseRoutes.Plugins.Handle("/statuses", api.ApiSessionRequired(getPluginStatuses)).Methods("GET") api.BaseRoutes.Plugins.Handle("/statuses", api.ApiSessionRequired(getPluginStatuses)).Methods("GET")
api.BaseRoutes.Plugin.Handle("/enable", api.ApiSessionRequired(enablePlugin)).Methods("POST") api.BaseRoutes.Plugin.Handle("/enable", api.ApiSessionRequired(enablePlugin)).Methods("POST")
@@ -42,7 +44,8 @@ func (api *API) InitPlugin() {
} }
func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) { func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) {
if !*c.App.Config().PluginSettings.Enable || !*c.App.Config().PluginSettings.EnableUploads { config := c.App.Config()
if !*config.PluginSettings.Enable || !*config.PluginSettings.EnableUploads || *config.PluginSettings.RequirePluginSignature {
c.Err = model.NewAppError("uploadPlugin", "app.plugin.upload_disabled.app_error", nil, "", http.StatusNotImplemented) c.Err = model.NewAppError("uploadPlugin", "app.plugin.upload_disabled.app_error", nil, "", http.StatusNotImplemented)
return return
} }
@@ -81,19 +84,12 @@ func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) {
if len(m.Value["force"]) > 0 && m.Value["force"][0] == "true" { if len(m.Value["force"]) > 0 && m.Value["force"][0] == "true" {
force = true force = true
} }
manifest, unpackErr := c.App.InstallPlugin(file, force)
if unpackErr != nil { installPlugin(c, w, file, force)
c.Err = unpackErr
return
}
w.WriteHeader(http.StatusCreated)
w.Write([]byte(manifest.ToJson()))
} }
func installPluginFromUrl(c *Context, w http.ResponseWriter, r *http.Request) { func installPluginFromUrl(c *Context, w http.ResponseWriter, r *http.Request) {
if !*c.App.Config().PluginSettings.Enable { if !*c.App.Config().PluginSettings.Enable || *c.App.Config().PluginSettings.RequirePluginSignature {
c.Err = model.NewAppError("installPluginFromUrl", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) c.Err = model.NewAppError("installPluginFromUrl", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
return return
} }
@@ -103,46 +99,57 @@ func installPluginFromUrl(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
force := r.URL.Query().Get("force") == "true"
downloadUrl := r.URL.Query().Get("plugin_download_url") downloadUrl := r.URL.Query().Get("plugin_download_url")
if !model.IsValidHttpUrl(downloadUrl) { pluginFile, err := downloadFromUrl(c, downloadUrl)
c.Err = model.NewAppError("installPluginFromUrl", "api.plugin.install.invalid_url.app_error", nil, "", http.StatusBadRequest)
return
}
u, err := url.ParseRequestURI(downloadUrl)
if err != nil { if err != nil {
c.Err = model.NewAppError("installPluginFromUrl", "api.plugin.install.invalid_url.app_error", nil, "", http.StatusBadRequest) c.Err = err
return return
} }
if !*c.App.Config().PluginSettings.AllowInsecureDownloadUrl && u.Scheme != "https" { installPlugin(c, w, pluginFile, force)
c.Err = model.NewAppError("installPluginFromUrl", "api.plugin.install.insecure_url.app_error", nil, "", http.StatusBadRequest) }
func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
if !*c.App.Config().PluginSettings.Enable {
c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
return return
} }
client := c.App.HTTPService.MakeClient(true) if !*c.App.Config().PluginSettings.EnableMarketplace {
client.Timeout = INSTALL_PLUGIN_FROM_URL_HTTP_REQUEST_TIMEOUT c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.marketplace_disabled.app_error", nil, "", http.StatusNotImplemented)
return
}
resp, err := client.Get(downloadUrl) if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
pluginRequest, err := model.PluginRequestFromReader(r.Body)
if err != nil { if err != nil {
c.Err = model.NewAppError("installPluginFromUrl", "api.plugin.install.download_failed.app_error", nil, err.Error(), http.StatusBadRequest) c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.marketplace_plugin_request.app_error", nil, err.Error(), http.StatusNotImplemented)
return return
} }
defer resp.Body.Close() plugin, appErr := c.App.GetMarketplacePlugin(pluginRequest)
if appErr != nil {
force := false c.Err = appErr
if r.URL.Query().Get("force") == "true" { return
force = true
} }
fileBytes, err := ioutil.ReadAll(resp.Body) pluginFile, appErr := downloadFromUrl(c, plugin.DownloadURL)
if appErr != nil {
c.Err = appErr
return
}
signature, err := plugin.DecodeSignature()
if err != nil { if err != nil {
c.Err = model.NewAppError("installPluginFromUrl", "api.plugin.install.reading_stream_failed.app_error", nil, err.Error(), http.StatusBadRequest) c.Err = model.NewAppError("installMarketplacePlugin", "app.plugin.signature_decode.app_error", nil, err.Error(), http.StatusNotImplemented)
return return
} }
manifest, appErr := c.App.InstallPlugin(bytes.NewReader(fileBytes), force) manifest, appErr := c.App.InstallPluginWithSignature(pluginFile, signature)
if appErr != nil { if appErr != nil {
c.Err = appErr c.Err = appErr
return return
@@ -348,3 +355,43 @@ func parseMarketplacePluginFilter(u *url.URL) (*model.MarketplacePluginFilter, e
ServerVersion: serverVersion, ServerVersion: serverVersion,
}, nil }, nil
} }
func downloadFromUrl(c *Context, downloadUrl string) (io.ReadSeeker, *model.AppError) {
if !model.IsValidHttpUrl(downloadUrl) {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.invalid_url.app_error", nil, "", http.StatusBadRequest)
}
u, err := url.ParseRequestURI(downloadUrl)
if err != nil {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.invalid_url.app_error", nil, "", http.StatusBadRequest)
}
if !*c.App.Config().PluginSettings.AllowInsecureDownloadUrl && u.Scheme != "https" {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.insecure_url.app_error", nil, "", http.StatusBadRequest)
}
client := c.App.HTTPService.MakeClient(true)
client.Timeout = INSTALL_PLUGIN_FROM_URL_HTTP_REQUEST_TIMEOUT
resp, err := client.Get(downloadUrl)
if err != nil {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.download_failed.app_error", nil, err.Error(), http.StatusBadRequest)
}
defer resp.Body.Close()
fileBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, model.NewAppError("downloadFromUrl", "api.plugin.install.reading_stream_failed.app_error", nil, err.Error(), http.StatusBadRequest)
}
return bytes.NewReader(fileBytes), nil
}
func installPlugin(c *Context, w http.ResponseWriter, plugin io.ReadSeeker, force bool) {
manifest, appErr := c.App.InstallPlugin(plugin, force)
if appErr != nil {
c.Err = appErr
return
}
w.WriteHeader(http.StatusCreated)
w.Write([]byte(manifest.ToJson()))
}

Просмотреть файл

@@ -5,6 +5,7 @@ package api4
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@@ -206,7 +207,7 @@ func TestPlugin(t *testing.T) {
// Deactivate error case // Deactivate error case
ok, resp = th.SystemAdminClient.DisablePlugin("junk") ok, resp = th.SystemAdminClient.DisablePlugin("junk")
CheckBadRequestStatus(t, resp) CheckNotFoundStatus(t, resp)
assert.False(t, ok) assert.False(t, ok)
// Get error cases // Get error cases
@@ -241,7 +242,7 @@ func TestPlugin(t *testing.T) {
// Remove error cases // Remove error cases
ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id) ok, resp = th.SystemAdminClient.RemovePlugin(manifest.Id)
CheckBadRequestStatus(t, resp) CheckNotFoundStatus(t, resp)
assert.False(t, ok) assert.False(t, ok)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false })
@@ -253,7 +254,7 @@ func TestPlugin(t *testing.T) {
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
_, resp = th.SystemAdminClient.RemovePlugin("bad.id") _, resp = th.SystemAdminClient.RemovePlugin("bad.id")
CheckBadRequestStatus(t, resp) CheckNotFoundStatus(t, resp)
} }
func TestNotifyClusterPluginEvent(t *testing.T) { func TestNotifyClusterPluginEvent(t *testing.T) {
@@ -543,9 +544,9 @@ func TestGetInstalledMarketplacePlugins(t *testing.T) {
samplePlugins := []*model.MarketplacePlugin{ samplePlugins := []*model.MarketplacePlugin{
{ {
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "https://github.com/mattermost/mattermost-plugin-nps", HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
IconData: "http://example.com/icon.svg", IconData: "https://example.com/icon.svg",
DownloadURL: "https://github.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz", DownloadURL: "https://example.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz",
Manifest: &model.Manifest{ Manifest: &model.Manifest{
Id: "com.mattermost.nps", Id: "com.mattermost.nps",
Name: "User Satisfaction Surveys", Name: "User Satisfaction Surveys",
@@ -671,9 +672,9 @@ func TestSearchGetMarketplacePlugins(t *testing.T) {
samplePlugins := []*model.MarketplacePlugin{ samplePlugins := []*model.MarketplacePlugin{
{ {
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "https://github.com/mattermost/mattermost-plugin-nps", HomepageURL: "example.com/mattermost/mattermost-plugin-nps",
IconData: "Cjxzdmcgdmlld0JveD0nMCAwIDEwNSA5MycgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJz4KPHBhdGggZD0nTTY2LDBoMzl2OTN6TTM4LDBoLTM4djkzek01MiwzNWwyNSw1OGgtMTZsLTgtMThoLTE4eicgZmlsbD0nI0VEMUMyNCcvPgo8L3N2Zz4K", IconData: "Cjxzdmcgdmlld0JveD0nMCAwIDEwNSA5MycgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJz4KPHBhdGggZD0nTTY2LDBoMzl2OTN6TTM4LDBoLTM4djkzek01MiwzNWwyNSw1OGgtMTZsLTgtMThoLTE4eicgZmlsbD0nI0VEMUMyNCcvPgo8L3N2Zz4K",
DownloadURL: "https://github.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz", DownloadURL: "example.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz",
Manifest: &model.Manifest{ Manifest: &model.Manifest{
Id: "com.mattermost.nps", Id: "com.mattermost.nps",
Name: "User Satisfaction Surveys", Name: "User Satisfaction Surveys",
@@ -769,6 +770,184 @@ func TestSearchGetMarketplacePlugins(t *testing.T) {
}) })
} }
func TestInstallMarketplacePlugin(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.EnableUploads = true
*cfg.PluginSettings.EnableMarketplace = false
})
path, _ := fileutils.FindDir("tests")
signatureFilename := "testpluginv2.tar.gz.sig"
signatureFileReader, err := os.Open(filepath.Join(path, signatureFilename))
require.Nil(t, err)
sigFile, err := ioutil.ReadAll(signatureFileReader)
require.Nil(t, err)
pluginSignature := base64.StdEncoding.EncodeToString(sigFile)
tarData, err := ioutil.ReadFile(filepath.Join(path, "testpluginv2.tar.gz"))
require.NoError(t, err)
pluginServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
res.Write(tarData)
}))
defer pluginServer.Close()
samplePlugins := []*model.MarketplacePlugin{
{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
IconData: "https://example.com/icon.svg",
DownloadURL: pluginServer.URL,
Manifest: &model.Manifest{
Id: "testplugin_v2",
Name: "testplugin_v2",
Description: "dsgsdg_v2",
Version: "1.2.2",
MinServerVersion: "",
},
},
InstalledVersion: "",
},
{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
IconData: "https://example.com/icon.svg",
DownloadURL: pluginServer.URL,
Manifest: &model.Manifest{
Id: "testplugin_v2",
Name: "testplugin_v2",
Description: "dsgsdg_v2",
Version: "1.2.3",
MinServerVersion: "",
},
Signature: pluginSignature,
},
InstalledVersion: "",
},
}
request := &model.InstallMarketplacePluginRequest{Id: "", Version: ""}
t.Run("marketplace disabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = false
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
})
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(request)
CheckNotImplementedStatus(t, resp)
require.Nil(t, plugin)
})
t.Run("RequirePluginSignature enabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.RequirePluginSignature = true
})
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
CheckNotImplementedStatus(t, resp)
require.Nil(t, manifest)
manifest, resp = th.SystemAdminClient.InstallPluginFromUrl("some_url", true)
CheckNotImplementedStatus(t, resp)
require.Nil(t, manifest)
})
t.Run("no server", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
})
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(request)
CheckInternalErrorStatus(t, resp)
require.Nil(t, plugin)
})
t.Run("no permission", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
})
plugin, resp := th.Client.InstallMarketplacePlugin(request)
CheckForbiddenStatus(t, resp)
require.Nil(t, plugin)
})
t.Run("plugin not found on the server", func(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
json, err := json.Marshal([]*model.MarketplacePlugin{})
require.NoError(t, err)
res.Write(json)
}))
defer testServer.Close()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
pRequest := &model.InstallMarketplacePluginRequest{Id: "some_plugin_id", Version: "0.0.1"}
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckInternalErrorStatus(t, resp)
require.Nil(t, plugin)
})
t.Run("plugin not verified", func(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
json, err := json.Marshal([]*model.MarketplacePlugin{samplePlugins[0]})
require.NoError(t, err)
res.Write(json)
}))
defer testServer.Close()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
*cfg.PluginSettings.AllowInsecureDownloadUrl = true
})
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin_v2", Version: "1.2.2"}
plugin, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckInternalErrorStatus(t, resp)
require.Nil(t, plugin)
})
t.Run("verify, install and remove plugin", func(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
json, err := json.Marshal([]*model.MarketplacePlugin{samplePlugins[1]})
require.NoError(t, err)
res.Write(json)
}))
defer testServer.Close()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
pRequest := &model.InstallMarketplacePluginRequest{Id: "testplugin_v2", Version: "1.2.3"}
manifest, resp := th.SystemAdminClient.InstallMarketplacePlugin(pRequest)
CheckNoError(t, resp)
require.NotNil(t, manifest)
require.Equal(t, "testplugin_v2", manifest.Id)
require.Equal(t, "1.2.3", manifest.Version)
filePath := filepath.Join(*th.App.Config().PluginSettings.Directory, "testplugin_v2.sig")
savedSigFile, err := th.App.ReadFile(filePath)
require.Nil(t, err)
require.EqualValues(t, sigFile, savedSigFile)
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
exists, err := th.App.FileExists(filePath)
require.Nil(t, err)
require.False(t, exists)
})
}
func findClusterMessages(event string, msgs []*model.ClusterMessage) []*model.ClusterMessage { func findClusterMessages(event string, msgs []*model.ClusterMessage) []*model.ClusterMessage {
var result []*model.ClusterMessage var result []*model.ClusterMessage
for _, msg := range msgs { for _, msg := range msgs {

Просмотреть файл

@@ -7,9 +7,9 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestSaveReaction(t *testing.T) { func TestSaveReaction(t *testing.T) {
@@ -34,35 +34,22 @@ func TestSaveReaction(t *testing.T) {
t.Run("successful-reaction", func(t *testing.T) { t.Run("successful-reaction", func(t *testing.T) {
rr, resp := Client.SaveReaction(reaction) rr, resp := Client.SaveReaction(reaction)
CheckNoError(t, resp) CheckNoError(t, resp)
require.Equal(t, reaction.UserId, rr.UserId, "UserId did not match")
require.Equal(t, reaction.PostId, rr.PostId, "PostId did not match")
require.Equal(t, reaction.EmojiName, rr.EmojiName, "EmojiName did not match")
require.NotEqual(t, 0, rr.CreateAt, "CreateAt should exist")
if rr.UserId != reaction.UserId { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("UserId did not match") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "didn't save reaction correctly")
if rr.PostId != reaction.PostId {
t.Fatal("PostId did not match")
}
if rr.EmojiName != reaction.EmojiName {
t.Fatal("EmojiName did not match")
}
if rr.CreateAt == 0 {
t.Fatal("CreateAt should exist")
}
if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 1 {
t.Fatal("didn't save reaction correctly")
}
}) })
t.Run("duplicated-reaction", func(t *testing.T) { t.Run("duplicated-reaction", func(t *testing.T) {
_, resp := Client.SaveReaction(reaction) _, resp := Client.SaveReaction(reaction)
CheckNoError(t, resp) CheckNoError(t, resp)
reactions, err := th.App.GetReactionsForPost(postId)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 1 { require.Nil(t, err)
t.Fatal("should have not save duplicated reaction") require.Equal(t, 1, len(reactions), "should have not save duplicated reaction")
}
}) })
t.Run("save-second-reaction", func(t *testing.T) { t.Run("save-second-reaction", func(t *testing.T) {
@@ -70,14 +57,11 @@ func TestSaveReaction(t *testing.T) {
rr, resp := Client.SaveReaction(reaction) rr, resp := Client.SaveReaction(reaction)
CheckNoError(t, resp) CheckNoError(t, resp)
require.Equal(t, rr.EmojiName, reaction.EmojiName, "EmojiName did not match")
if rr.EmojiName != reaction.EmojiName { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("EmojiName did not match") require.Nil(t, err, "error saving multiple reactions")
} require.Equal(t, len(reactions), 2, "should have save multiple reactions")
if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 2 {
t.Fatal("should have save multiple reactions")
}
}) })
t.Run("saving-special-case", func(t *testing.T) { t.Run("saving-special-case", func(t *testing.T) {
@@ -85,14 +69,11 @@ func TestSaveReaction(t *testing.T) {
rr, resp := Client.SaveReaction(reaction) rr, resp := Client.SaveReaction(reaction)
CheckNoError(t, resp) CheckNoError(t, resp)
require.Equal(t, reaction.EmojiName, rr.EmojiName, "EmojiName did not match")
if rr.EmojiName != reaction.EmojiName { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("EmojiName did not match") require.Nil(t, err)
} require.Equal(t, 3, len(reactions), "should have save multiple reactions")
if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 3 {
t.Fatal("should have save multiple reactions")
}
}) })
t.Run("react-to-not-existing-post-id", func(t *testing.T) { t.Run("react-to-not-existing-post-id", func(t *testing.T) {
@@ -167,9 +148,9 @@ func TestSaveReaction(t *testing.T) {
_, resp := Client.SaveReaction(reaction) _, resp := Client.SaveReaction(reaction)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 3 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("should have not created a reactions") require.Nil(t, err)
} require.Equal(t, 3, len(reactions), "should have not created a reactions")
th.AddPermissionToRole(model.PERMISSION_ADD_REACTION.Id, model.CHANNEL_USER_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_ADD_REACTION.Id, model.CHANNEL_USER_ROLE_ID)
}) })
@@ -192,9 +173,9 @@ func TestSaveReaction(t *testing.T) {
_, resp := Client.SaveReaction(reaction) _, resp := Client.SaveReaction(reaction)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
if reactions, err := th.App.GetReactionsForPost(post.Id); err != nil || len(reactions) != 0 { reactions, err := th.App.GetReactionsForPost(post.Id)
t.Fatal("should have not created a reaction") require.Nil(t, err)
} require.Equal(t, 0, len(reactions), "should have not created a reaction")
th.App.RemoveLicense() th.App.RemoveLicense()
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false })
@@ -218,9 +199,9 @@ func TestSaveReaction(t *testing.T) {
_, resp := Client.SaveReaction(reaction) _, resp := Client.SaveReaction(reaction)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
if reactions, err := th.App.GetReactionsForPost(post.Id); err != nil || len(reactions) != 0 { reactions, err := th.App.GetReactionsForPost(post.Id)
t.Fatal("should have not created a reaction") require.Nil(t, err)
} require.Equal(t, 0, len(reactions), "should have not created a reaction")
}) })
} }
@@ -263,12 +244,10 @@ func TestGetReactions(t *testing.T) {
var reactions []*model.Reaction var reactions []*model.Reaction
for _, userReaction := range userReactions { for _, userReaction := range userReactions {
if reaction, err := th.App.Srv.Store.Reaction().Save(userReaction); err != nil { reaction, err := th.App.Srv.Store.Reaction().Save(userReaction)
t.Fatal(err) require.Nil(t, err)
} else {
reactions = append(reactions, reaction) reactions = append(reactions, reaction)
} }
}
t.Run("get-reactions", func(t *testing.T) { t.Run("get-reactions", func(t *testing.T) {
rr, resp := Client.GetReactions(postId) rr, resp := Client.GetReactions(postId)
@@ -345,70 +324,68 @@ func TestDeleteReaction(t *testing.T) {
t.Run("delete-reaction", func(t *testing.T) { t.Run("delete-reaction", func(t *testing.T) {
th.App.SaveReactionForPost(r1) th.App.SaveReactionForPost(r1)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("didn't save reaction correctly") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "didn't save reaction correctly")
ok, resp := Client.DeleteReaction(r1) ok, resp := Client.DeleteReaction(r1)
CheckNoError(t, resp) CheckNoError(t, resp)
if !ok { require.True(t, ok, "should have returned true")
t.Fatal("should have returned true")
}
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 0 { reactions, err = th.App.GetReactionsForPost(postId)
t.Fatal("should have deleted reaction") require.Nil(t, err)
} require.Equal(t, 0, len(reactions), "should have deleted reaction")
}) })
t.Run("delete-reaction-when-post-has-multiple-reactions", func(t *testing.T) { t.Run("delete-reaction-when-post-has-multiple-reactions", func(t *testing.T) {
th.App.SaveReactionForPost(r1) th.App.SaveReactionForPost(r1)
th.App.SaveReactionForPost(r2) th.App.SaveReactionForPost(r2)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("didn't save reactions correctly") require.Nil(t, err)
} require.Equal(t, len(reactions), 2, "didn't save reactions correctly")
_, resp := Client.DeleteReaction(r2) _, resp := Client.DeleteReaction(r2)
CheckNoError(t, resp) CheckNoError(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 || *reactions[0] != *r1 { reactions, err = th.App.GetReactionsForPost(postId)
t.Fatal("should have deleted 1 reaction only") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "should have deleted only 1 reaction")
require.Equal(t, *r1, *reactions[0], "should have deleted 1 reaction only")
}) })
t.Run("delete-reaction-when-plus-one-reaction-name", func(t *testing.T) { t.Run("delete-reaction-when-plus-one-reaction-name", func(t *testing.T) {
th.App.SaveReactionForPost(r3) th.App.SaveReactionForPost(r3)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("didn't save reactions correctly") require.Nil(t, err)
} require.Equal(t, 2, len(reactions), "didn't save reactions correctly")
_, resp := Client.DeleteReaction(r3) _, resp := Client.DeleteReaction(r3)
CheckNoError(t, resp) CheckNoError(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 || *reactions[0] != *r1 { reactions, err = th.App.GetReactionsForPost(postId)
t.Fatal("should have deleted 1 reaction only") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "should have deleted 1 reaction only")
require.Equal(t, *r1, *reactions[0], "should have deleted 1 reaction only")
}) })
t.Run("delete-reaction-made-by-another-user", func(t *testing.T) { t.Run("delete-reaction-made-by-another-user", func(t *testing.T) {
th.LoginBasic2() th.LoginBasic2()
th.App.SaveReactionForPost(r4) th.App.SaveReactionForPost(r4)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("didn't save reaction correctly") require.Nil(t, err)
} require.Equal(t, 2, len(reactions), "didn't save reaction correctly")
th.LoginBasic() th.LoginBasic()
ok, resp := Client.DeleteReaction(r4) ok, resp := Client.DeleteReaction(r4)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
if ok { require.False(t, ok, "should have returned false")
t.Fatal("should have returned false")
}
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { reactions, err = th.App.GetReactionsForPost(postId)
t.Fatal("should have not deleted a reaction") require.Nil(t, err)
} require.Equal(t, 2, len(reactions), "should have not deleted a reaction")
}) })
t.Run("delete-reaction-from-not-existing-post-id", func(t *testing.T) { t.Run("delete-reaction-from-not-existing-post-id", func(t *testing.T) {
@@ -469,9 +446,9 @@ func TestDeleteReaction(t *testing.T) {
_, resp = th.SystemAdminClient.DeleteReaction(r4) _, resp = th.SystemAdminClient.DeleteReaction(r4)
CheckNoError(t, resp) CheckNoError(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 0 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("should have deleted both reactions") require.Nil(t, err)
} require.Equal(t, 0, len(reactions), "should have deleted both reactions")
}) })
t.Run("unable-to-delete-reaction-without-permissions", func(t *testing.T) { t.Run("unable-to-delete-reaction-without-permissions", func(t *testing.T) {
@@ -483,9 +460,9 @@ func TestDeleteReaction(t *testing.T) {
_, resp := Client.DeleteReaction(r1) _, resp := Client.DeleteReaction(r1)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("should have not deleted a reactions") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "should have not deleted a reactions")
th.AddPermissionToRole(model.PERMISSION_REMOVE_REACTION.Id, model.CHANNEL_USER_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_REMOVE_REACTION.Id, model.CHANNEL_USER_ROLE_ID)
}) })
@@ -496,9 +473,9 @@ func TestDeleteReaction(t *testing.T) {
_, resp := th.SystemAdminClient.DeleteReaction(r1) _, resp := th.SystemAdminClient.DeleteReaction(r1)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("should have not deleted a reactions") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "should have not deleted a reactions")
th.AddPermissionToRole(model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id, model.SYSTEM_ADMIN_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id, model.SYSTEM_ADMIN_ROLE_ID)
}) })
@@ -520,18 +497,18 @@ func TestDeleteReaction(t *testing.T) {
r1, resp := Client.SaveReaction(reaction) r1, resp := Client.SaveReaction(reaction)
CheckNoError(t, resp) CheckNoError(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("should have created a reaction") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "should have created a reaction")
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true })
_, resp = th.SystemAdminClient.DeleteReaction(r1) _, resp = th.SystemAdminClient.DeleteReaction(r1)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { reactions, err = th.App.GetReactionsForPost(postId)
t.Fatal("should have not deleted a reaction") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "should have not deleted a reaction")
th.App.RemoveLicense() th.App.RemoveLicense()
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false })
@@ -552,19 +529,19 @@ func TestDeleteReaction(t *testing.T) {
r1, resp := Client.SaveReaction(reaction) r1, resp := Client.SaveReaction(reaction)
CheckNoError(t, resp) CheckNoError(t, resp)
if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { reactions, err := th.App.GetReactionsForPost(postId)
t.Fatal("should have created a reaction") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "should have created a reaction")
err := th.App.DeleteChannel(channel, userId) err = th.App.DeleteChannel(channel, userId)
assert.Nil(t, err) assert.Nil(t, err)
_, resp = Client.SaveReaction(r1) _, resp = Client.SaveReaction(r1)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
if reactions, err := th.App.GetReactionsForPost(post.Id); err != nil || len(reactions) != 1 { reactions, err = th.App.GetReactionsForPost(post.Id)
t.Fatal("should have not deleted a reaction") require.Nil(t, err)
} require.Equal(t, 1, len(reactions), "should have not deleted a reaction")
}) })
} }
@@ -618,12 +595,9 @@ func TestGetBulkReactions(t *testing.T) {
for _, userReaction := range userReactions { for _, userReaction := range userReactions {
reactions := expectedPostIdsReactionsMap[userReaction.PostId] reactions := expectedPostIdsReactionsMap[userReaction.PostId]
if reaction, err := th.App.Srv.Store.Reaction().Save(userReaction); err != nil { reaction, err := th.App.Srv.Store.Reaction().Save(userReaction)
t.Fatal(err) require.Nil(t, err)
} else {
reactions = append(reactions, reaction) reactions = append(reactions, reaction)
}
expectedPostIdsReactionsMap[userReaction.PostId] = reactions expectedPostIdsReactionsMap[userReaction.PostId] = reactions
} }

Просмотреть файл

@@ -410,7 +410,27 @@ func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) {
} }
err := c.App.SendAckToPushProxy(ack) err := c.App.SendAckToPushProxy(ack)
if ack.NotificationType == model.PUSH_TYPE_ID_LOADED {
if err != nil { if err != nil {
// Log the error only, then continue to fetch notification message
c.App.NotificationsLog.Error("Notification ack not sent to push proxy",
mlog.String("ackId", ack.Id),
mlog.String("type", ack.NotificationType),
mlog.String("postId", ack.PostId),
mlog.String("status", err.Error()),
)
}
msg, appErr := c.App.BuildFetchedPushNotificationMessage(ack.PostId, c.App.Session.UserId)
if appErr != nil {
c.Err = model.NewAppError("pushNotificationAck", "api.push_notification.id_loaded.fetch.app_error", nil, appErr.Error(), http.StatusInternalServerError)
return
}
w.Write([]byte(msg.ToJson()))
return
} else if err != nil {
c.Err = model.NewAppError("pushNotificationAck", "api.push_notifications_ack.forward.app_error", nil, err.Error(), http.StatusInternalServerError) c.Err = model.NewAppError("pushNotificationAck", "api.push_notifications_ack.forward.app_error", nil, err.Error(), http.StatusInternalServerError)
return return
} }

Просмотреть файл

@@ -102,6 +102,7 @@ func TestCreateTeamSanitization(t *testing.T) {
rteam, resp := th.Client.CreateTeam(team) rteam, resp := th.Client.CreateTeam(team)
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
}) })
t.Run("system admin", func(t *testing.T) { t.Run("system admin", func(t *testing.T) {
@@ -116,6 +117,7 @@ func TestCreateTeamSanitization(t *testing.T) {
rteam, resp := th.SystemAdminClient.CreateTeam(team) rteam, resp := th.SystemAdminClient.CreateTeam(team)
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
}) })
} }
@@ -187,18 +189,37 @@ func TestGetTeamSanitization(t *testing.T) {
CheckNoError(t, resp) CheckNoError(t, resp)
require.Empty(t, rteam.Email, "should have sanitized email") require.Empty(t, rteam.Email, "should have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
})
t.Run("team user without invite permissions", func(t *testing.T) {
th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID)
th.LinkUserToTeam(th.BasicUser2, team)
client := th.CreateClient()
th.LoginBasic2WithClient(client)
rteam, resp := client.GetTeam(team.Id, "")
CheckNoError(t, resp)
require.Empty(t, rteam.Email, "should have sanitized email")
require.Empty(t, rteam.InviteId, "should have sanitized inviteid")
}) })
t.Run("team admin", func(t *testing.T) { t.Run("team admin", func(t *testing.T) {
rteam, resp := th.Client.GetTeam(team.Id, "") rteam, resp := th.Client.GetTeam(team.Id, "")
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
}) })
t.Run("system admin", func(t *testing.T) { t.Run("system admin", func(t *testing.T) {
rteam, resp := th.SystemAdminClient.GetTeam(team.Id, "") rteam, resp := th.SystemAdminClient.GetTeam(team.Id, "")
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
}) })
} }
@@ -337,13 +358,17 @@ func TestUpdateTeamSanitization(t *testing.T) {
t.Run("team admin", func(t *testing.T) { t.Run("team admin", func(t *testing.T) {
rteam, resp := th.Client.UpdateTeam(team) rteam, resp := th.Client.UpdateTeam(team)
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email for admin") require.NotEmpty(t, rteam.Email, "should not have sanitized email for admin")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
}) })
t.Run("system admin", func(t *testing.T) { t.Run("system admin", func(t *testing.T) {
rteam, resp := th.SystemAdminClient.UpdateTeam(team) rteam, resp := th.SystemAdminClient.UpdateTeam(team)
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email for admin") require.NotEmpty(t, rteam.Email, "should not have sanitized email for admin")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
}) })
} }
@@ -421,13 +446,17 @@ func TestPatchTeamSanitization(t *testing.T) {
t.Run("team admin", func(t *testing.T) { t.Run("team admin", func(t *testing.T) {
rteam, resp := th.Client.PatchTeam(team.Id, &model.TeamPatch{}) rteam, resp := th.Client.PatchTeam(team.Id, &model.TeamPatch{})
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email for admin") require.NotEmpty(t, rteam.Email, "should not have sanitized email for admin")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
}) })
t.Run("system admin", func(t *testing.T) { t.Run("system admin", func(t *testing.T) {
rteam, resp := th.SystemAdminClient.PatchTeam(team.Id, &model.TeamPatch{}) rteam, resp := th.SystemAdminClient.PatchTeam(team.Id, &model.TeamPatch{})
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email for admin") require.NotEmpty(t, rteam.Email, "should not have sanitized email for admin")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
}) })
} }
@@ -702,9 +731,11 @@ func TestGetAllTeamsSanitization(t *testing.T) {
if rteam.Id == team.Id { if rteam.Id == team.Id {
teamFound = true teamFound = true
require.NotEmpty(t, rteam.Email, "should not have sanitized email for team admin") require.NotEmpty(t, rteam.Email, "should not have sanitized email for team admin")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
} else if rteam.Id == team2.Id { } else if rteam.Id == team2.Id {
team2Found = true team2Found = true
require.Empty(t, rteam.Email, "should've sanitized email for non-admin") require.Empty(t, rteam.Email, "should have sanitized email for team admin")
require.Empty(t, rteam.InviteId, "should have sanitized inviteid")
} }
} }
@@ -721,6 +752,7 @@ func TestGetAllTeamsSanitization(t *testing.T) {
} }
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should not have sanitized inviteid")
} }
}) })
} }
@@ -791,19 +823,40 @@ func TestGetTeamByNameSanitization(t *testing.T) {
rteam, resp := client.GetTeamByName(team.Name, "") rteam, resp := client.GetTeamByName(team.Name, "")
CheckNoError(t, resp) CheckNoError(t, resp)
require.Empty(t, rteam.Email, "should've sanitized email") require.Empty(t, rteam.Email, "should've sanitized email")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
})
t.Run("team user without invite permissions", func(t *testing.T) {
th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID)
th.LinkUserToTeam(th.BasicUser2, team)
client := th.CreateClient()
th.LoginBasic2WithClient(client)
rteam, resp := client.GetTeam(team.Id, "")
CheckNoError(t, resp)
require.Empty(t, rteam.Email, "should have sanitized email")
require.Empty(t, rteam.InviteId, "should have sanitized inviteid")
}) })
t.Run("team admin/non-admin", func(t *testing.T) { t.Run("team admin/non-admin", func(t *testing.T) {
rteam, resp := th.Client.GetTeamByName(team.Name, "") rteam, resp := th.Client.GetTeamByName(team.Name, "")
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
}) })
t.Run("system admin", func(t *testing.T) { t.Run("system admin", func(t *testing.T) {
rteam, resp := th.SystemAdminClient.GetTeamByName(team.Name, "") rteam, resp := th.SystemAdminClient.GetTeamByName(team.Name, "")
CheckNoError(t, resp) CheckNoError(t, resp)
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
}) })
} }
@@ -899,6 +952,7 @@ func TestSearchAllTeamsSanitization(t *testing.T) {
for _, rteam := range rteams { for _, rteam := range rteams {
require.Empty(t, rteam.Email, "should've sanitized email") require.Empty(t, rteam.Email, "should've sanitized email")
require.Empty(t, rteam.AllowedDomains, "should've sanitized allowed domains") require.Empty(t, rteam.AllowedDomains, "should've sanitized allowed domains")
require.Empty(t, rteam.InviteId, "should have sanitized inviteid")
} }
}) })
@@ -913,6 +967,7 @@ func TestSearchAllTeamsSanitization(t *testing.T) {
for _, rteam := range rteams { for _, rteam := range rteams {
require.Empty(t, rteam.Email, "should've sanitized email") require.Empty(t, rteam.Email, "should've sanitized email")
require.Empty(t, rteam.AllowedDomains, "should've sanitized allowed domains") require.Empty(t, rteam.AllowedDomains, "should've sanitized allowed domains")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
} }
}) })
@@ -922,6 +977,7 @@ func TestSearchAllTeamsSanitization(t *testing.T) {
for _, rteam := range rteams { for _, rteam := range rteams {
if rteam.Id == team.Id || rteam.Id == team2.Id || rteam.Id == th.BasicTeam.Id { if rteam.Id == team.Id || rteam.Id == team2.Id || rteam.Id == th.BasicTeam.Id {
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
} }
} }
}) })
@@ -931,6 +987,7 @@ func TestSearchAllTeamsSanitization(t *testing.T) {
CheckNoError(t, resp) CheckNoError(t, resp)
for _, rteam := range rteams { for _, rteam := range rteams {
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
} }
}) })
} }
@@ -1010,6 +1067,27 @@ func TestGetTeamsForUserSanitization(t *testing.T) {
} }
require.Empty(t, rteam.Email, "should've sanitized email") require.Empty(t, rteam.Email, "should've sanitized email")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
}
})
t.Run("team user without invite permissions", func(t *testing.T) {
th.LinkUserToTeam(th.BasicUser2, team)
th.LinkUserToTeam(th.BasicUser2, team2)
client := th.CreateClient()
th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID)
th.LoginBasic2WithClient(client)
rteams, resp := client.GetTeamsForUser(th.BasicUser2.Id, "")
CheckNoError(t, resp)
for _, rteam := range rteams {
if rteam.Id != team.Id && rteam.Id != team2.Id {
continue
}
require.Empty(t, rteam.Email, "should have sanitized email")
require.Empty(t, rteam.InviteId, "should have sanitized inviteid")
} }
}) })
@@ -1022,6 +1100,7 @@ func TestGetTeamsForUserSanitization(t *testing.T) {
} }
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
} }
}) })
@@ -1034,6 +1113,7 @@ func TestGetTeamsForUserSanitization(t *testing.T) {
} }
require.NotEmpty(t, rteam.Email, "should not have sanitized email") require.NotEmpty(t, rteam.Email, "should not have sanitized email")
require.NotEmpty(t, rteam.InviteId, "should have not sanitized inviteid")
} }
}) })
} }

Просмотреть файл

@@ -1051,6 +1051,11 @@ func updateUserActive(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if active && user.IsGuest() && !*c.App.Config().GuestAccountsSettings.Enable {
c.Err = model.NewAppError("updateUserActive", "api.user.update_active.cannot_enable_guest_when_guest_feature_is_disabled.app_error", nil, "userId="+c.Params.UserId, http.StatusUnauthorized)
return
}
if _, err = c.App.UpdateActive(user, active); err != nil { if _, err = c.App.UpdateActive(user, active); err != nil {
c.Err = err c.Err = err
} }

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -139,7 +139,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
updateMentionChans = append(updateMentionChans, umc) updateMentionChans = append(updateMentionChans, umc)
} }
notification := &postNotification{ notification := &PostNotification{
post: post, post: post,
channel: channel, channel: channel,
profileMap: profileMap, profileMap: profileMap,
@@ -691,7 +691,7 @@ func addMentionKeywordsForUser(keywords map[string][]string, profile *model.User
} }
// Represents either an email or push notification and contains the fields required to send it to any user. // Represents either an email or push notification and contains the fields required to send it to any user.
type postNotification struct { type PostNotification struct {
channel *model.Channel channel *model.Channel
post *model.Post post *model.Post
profileMap map[string]*model.User profileMap map[string]*model.User
@@ -701,7 +701,7 @@ type postNotification struct {
// Returns the name of the channel for this notification. For direct messages, this is the sender's name // Returns the name of the channel for this notification. For direct messages, this is the sender's name
// preceeded by an at sign. For group messages, this is a comma-separated list of the members of the // preceeded by an at sign. For group messages, this is a comma-separated list of the members of the
// channel, with an option to exclude the recipient of the message from that list. // channel, with an option to exclude the recipient of the message from that list.
func (n *postNotification) GetChannelName(userNameFormat string, excludeId string) string { func (n *PostNotification) GetChannelName(userNameFormat, excludeId string) string {
switch n.channel.Type { switch n.channel.Type {
case model.CHANNEL_DIRECT: case model.CHANNEL_DIRECT:
return n.sender.GetDisplayNameWithPrefix(userNameFormat, "@") return n.sender.GetDisplayNameWithPrefix(userNameFormat, "@")
@@ -723,7 +723,7 @@ func (n *postNotification) GetChannelName(userNameFormat string, excludeId strin
// Returns the name of the sender of this notification, accounting for things like system messages // Returns the name of the sender of this notification, accounting for things like system messages
// and whether or not the username has been overridden by an integration. // and whether or not the username has been overridden by an integration.
func (n *postNotification) GetSenderName(userNameFormat string, overridesAllowed bool) string { func (n *PostNotification) GetSenderName(userNameFormat string, overridesAllowed bool) string {
if n.post.IsSystemMessage() { if n.post.IsSystemMessage() {
return utils.T("system.message.name") return utils.T("system.message.name")
} }

Просмотреть файл

@@ -18,7 +18,7 @@ import (
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
) )
func (a *App) sendNotificationEmail(notification *postNotification, user *model.User, team *model.Team) *model.AppError { func (a *App) sendNotificationEmail(notification *PostNotification, user *model.User, team *model.Team) *model.AppError {
channel := notification.channel channel := notification.channel
post := notification.post post := notification.post

Просмотреть файл

@@ -110,7 +110,7 @@ func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, use
return nil return nil
} }
func (a *App) sendPushNotification(notification *postNotification, user *model.User, explicitMention, channelWideMention bool, replyToThreadType string) { func (a *App) sendPushNotification(notification *PostNotification, user *model.User, explicitMention, channelWideMention bool, replyToThreadType string) {
cfg := a.Config() cfg := a.Config()
channel := notification.channel channel := notification.channel
post := notification.post post := notification.post
@@ -134,6 +134,22 @@ func (a *App) sendPushNotification(notification *postNotification, user *model.U
} }
} }
func (a *App) getFetchedPushNotificationMessage(postMessage, senderName, channelType string, hasFiles bool, userLocale i18n.TranslateFunc) string {
// If the post only has images then push an appropriate message
if len(postMessage) == 0 && hasFiles {
if channelType == model.CHANNEL_DIRECT {
return strings.Trim(userLocale("api.post.send_notifications_and_forget.push_image_only"), " ")
}
return senderName + userLocale("api.post.send_notifications_and_forget.push_image_only")
}
if channelType == model.CHANNEL_DIRECT {
return model.ClearMentionTags(postMessage)
}
return senderName + ": " + model.ClearMentionTags(postMessage)
}
func (a *App) getPushNotificationMessage(postMessage string, explicitMention, channelWideMention, hasFiles bool, func (a *App) getPushNotificationMessage(postMessage string, explicitMention, channelWideMention, hasFiles bool,
senderName, channelName, channelType, replyToThreadType string, userLocale i18n.TranslateFunc) string { senderName, channelName, channelType, replyToThreadType string, userLocale i18n.TranslateFunc) string {
@@ -428,9 +444,113 @@ func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *mo
return false return false
} }
func (a *App) BuildFetchedPushNotificationMessage(postId string, userId string) (model.PushNotification, *model.AppError) {
msg := model.PushNotification{
Type: model.PUSH_TYPE_ID_LOADED,
Category: model.CATEGORY_CAN_REPLY,
Version: model.PUSH_MESSAGE_V2,
}
post, err := a.GetSinglePost(postId)
if err != nil {
return msg, err
}
channel, err := a.GetChannel(post.ChannelId)
if err != nil {
return msg, err
}
user, err := a.GetUser(userId)
if err != nil {
return msg, err
}
sender, err := a.GetUser(post.UserId)
if err != nil {
return msg, err
}
msg.PostId = post.Id
msg.RootId = post.RootId
msg.SenderId = post.UserId
msg.ChannelId = channel.Id
msg.TeamId = channel.TeamId
notification := &PostNotification{
post: post,
channel: channel,
sender: sender,
}
cfg := a.Config()
nameFormat := a.GetNotificationNameFormat(user)
channelName := notification.GetChannelName(nameFormat, user.Id)
senderName := notification.GetSenderName(nameFormat, *cfg.ServiceSettings.EnablePostUsernameOverride)
msg.ChannelName = channelName
msg.SenderName = senderName
if ou, ok := post.Props["override_username"].(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride {
msg.OverrideUsername = ou
msg.SenderName = ou
}
if oi, ok := post.Props["override_icon_url"].(string); ok && *cfg.ServiceSettings.EnablePostIconOverride {
msg.OverrideIconUrl = oi
}
if fw, ok := post.Props["from_webhook"].(string); ok {
msg.FromWebhook = fw
}
userLocale := utils.GetUserTranslations(user.Locale)
hasFiles := post.FileIds != nil && len(post.FileIds) > 0
msg.Message = a.getFetchedPushNotificationMessage(post.Message, msg.SenderName, channel.Type, hasFiles, userLocale)
return msg, nil
}
func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
explicitMention bool, channelWideMention bool, replyToThreadType string) (*model.PushNotification, *model.AppError) { explicitMention bool, channelWideMention bool, replyToThreadType string) (*model.PushNotification, *model.AppError) {
var msg *model.PushNotification
cfg := a.Config()
contentsConfig := *cfg.EmailSettings.PushNotificationContents
if contentsConfig == model.ID_LOADED_NOTIFICATION {
msg = a.buildIdLoadedPushNotificationMessage(post, user)
} else {
msg = a.buildFullPushNotificationMessage(post, user, channel, channelName, senderName, explicitMention, channelWideMention, replyToThreadType)
}
badge, err := a.getPushNotificationBadge(user, channel)
if err != nil {
return nil, err
}
msg.Badge = badge
return msg, nil
}
func (a *App) buildIdLoadedPushNotificationMessage(post *model.Post, user *model.User) *model.PushNotification {
userLocale := utils.GetUserTranslations(user.Locale)
msg := &model.PushNotification{
PostId: post.Id,
ChannelId: post.ChannelId,
Category: model.CATEGORY_CAN_REPLY,
Version: model.PUSH_MESSAGE_V2,
Type: model.PUSH_TYPE_ID_LOADED,
Message: userLocale("api.push_notification.id_loaded.default_message"),
}
return msg
}
func (a *App) buildFullPushNotificationMessage(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
explicitMention bool, channelWideMention bool, replyToThreadType string) *model.PushNotification {
msg := &model.PushNotification{ msg := &model.PushNotification{
Category: model.CATEGORY_CAN_REPLY, Category: model.CATEGORY_CAN_REPLY,
Version: model.PUSH_MESSAGE_V2, Version: model.PUSH_MESSAGE_V2,
@@ -442,22 +562,6 @@ func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, c
SenderId: post.UserId, SenderId: post.UserId,
} }
if user.NotifyProps["push"] == "all" {
unreadCount, err := a.Srv.Store.User().GetAnyUnreadPostCountForChannel(user.Id, channel.Id)
if err != nil {
return nil, err
}
msg.Badge = int(unreadCount)
} else {
unreadCount, err := a.Srv.Store.User().GetUnreadCount(user.Id)
if err != nil {
return nil, err
}
msg.Badge = int(unreadCount)
}
cfg := a.Config() cfg := a.Config()
contentsConfig := *cfg.EmailSettings.PushNotificationContents contentsConfig := *cfg.EmailSettings.PushNotificationContents
if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT { if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT {
@@ -483,5 +587,18 @@ func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, c
msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, msg.SenderName, channelName, channel.Type, replyToThreadType, userLocale) msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, msg.SenderName, channelName, channel.Type, replyToThreadType, userLocale)
return msg, nil return msg
}
func (a *App) getPushNotificationBadge(user *model.User, channel *model.Channel) (int, *model.AppError) {
var unreadCount int64
var err *model.AppError
if user.NotifyProps["push"] == "all" {
unreadCount, err = a.Srv.Store.User().GetAnyUnreadPostCountForChannel(user.Id, channel.Id)
} else {
unreadCount, err = a.Srv.Store.User().GetUnreadCount(user.Id)
}
return int(unreadCount), err
} }

Просмотреть файл

@@ -899,7 +899,7 @@ func TestGetPushNotificationMessage(t *testing.T) {
} }
} }
func TestBuildPushNotificationMessage(t *testing.T) { func TestBuildPushNotificationMessageMentions(t *testing.T) {
th := Setup(t).InitBasic() th := Setup(t).InitBasic()
defer th.TearDown() defer th.TearDown()
@@ -949,3 +949,67 @@ func TestBuildPushNotificationMessage(t *testing.T) {
}) })
} }
} }
func TestBuildPushNotificationMessageContents(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
team := th.CreateTeam()
sender := th.CreateUser()
receiver := th.CreateUser()
th.LinkUserToTeam(sender, team)
th.LinkUserToTeam(receiver, team)
channel := th.CreateChannel(team)
th.AddUserToChannel(sender, channel)
th.AddUserToChannel(receiver, channel)
post := th.CreatePost(channel)
explicitMention := false
channelWideMention := false
replyToThreadType := ""
receiverLocale := utils.GetUserTranslations(receiver.Locale)
for name, tc := range map[string]struct {
contentsConfig string
expectedMsg *model.PushNotification
}{
"only post ID, channel ID, and message included in push notification": {
contentsConfig: model.ID_LOADED_NOTIFICATION,
expectedMsg: &model.PushNotification{
PostId: post.Id,
ChannelId: post.ChannelId,
Category: model.CATEGORY_CAN_REPLY,
Version: model.PUSH_MESSAGE_V2,
Type: model.PUSH_TYPE_ID_LOADED,
Message: receiverLocale("api.push_notification.id_loaded.default_message"),
},
},
"full contents included in push notification": {
contentsConfig: model.GENERIC_NOTIFICATION,
expectedMsg: &model.PushNotification{
Category: model.CATEGORY_CAN_REPLY,
Version: model.PUSH_MESSAGE_V2,
Type: model.PUSH_TYPE_MESSAGE,
PostId: post.Id,
TeamId: channel.TeamId,
ChannelId: channel.Id,
ChannelName: channel.Name,
RootId: post.RootId,
SenderId: post.UserId,
SenderName: sender.Username,
Message: fmt.Sprintf("%s posted a message.", sender.Username),
},
},
} {
t.Run(name, func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.EmailSettings.PushNotificationContents = tc.contentsConfig })
msg, err := th.App.BuildPushNotificationMessage(post, receiver, channel, channel.Name, sender.Username, explicitMention, channelWideMention, replyToThreadType)
require.Nil(t, err)
assert.Equal(t, tc.expectedMsg, msg)
})
}
}

Просмотреть файл

@@ -1540,7 +1540,7 @@ func TestPostNotificationGetChannelName(t *testing.T) {
}, },
} { } {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
notification := &postNotification{ notification := &PostNotification{
channel: testCase.channel, channel: testCase.channel,
sender: sender, sender: sender,
profileMap: profileMap, profileMap: profileMap,
@@ -1625,7 +1625,7 @@ func TestPostNotificationGetSenderName(t *testing.T) {
post = testCase.post post = testCase.post
} }
notification := &postNotification{ notification := &PostNotification{
channel: channel, channel: channel,
post: post, post: post,
sender: sender, sender: sender,

Просмотреть файл

@@ -19,6 +19,12 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type pluginSignaturePath struct {
pluginId string
path string
signaturePath string
}
// GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and // GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and
// initialized. // initialized.
// //
@@ -164,10 +170,18 @@ func (a *App) InitPlugins(pluginDir, webappPluginDir string) {
return nil return nil
} }
if fileReader, err := os.Open(walkPath); err != nil { fileReader, err := os.Open(walkPath)
if err != nil {
mlog.Error("Failed to open prepackaged plugin", mlog.Err(err), mlog.String("path", walkPath)) mlog.Error("Failed to open prepackaged plugin", mlog.Err(err), mlog.String("path", walkPath))
} else if _, err := a.installPluginLocally(fileReader, true); err != nil { return nil
mlog.Error("Failed to unpack prepackaged plugin", mlog.Err(err), mlog.String("path", walkPath)) }
defer fileReader.Close()
mlog.Debug("Installing prepackaged plugin", mlog.String("path", walkPath))
_, appErr := a.installPluginLocally(fileReader, nil, installPluginLocallyOnlyIfNewOrUpgrade)
if appErr != nil {
mlog.Error("Failed to unpack prepackaged plugin", mlog.Err(appErr), mlog.String("path", walkPath))
} }
return nil return nil
@@ -226,35 +240,33 @@ func (a *App) SyncPlugins() *model.AppError {
} }
// Install plugins from the file store. // Install plugins from the file store.
fileStorePaths, appErr := a.ListDirectory(fileStorePluginFolder) pluginSignaturePathMap, appErr := a.getPluginsFromFolder()
if appErr != nil { if appErr != nil {
return model.NewAppError("SyncPlugins", "app.plugin.sync.list_filestore.app_error", nil, appErr.Error(), http.StatusInternalServerError) return appErr
} }
if len(fileStorePaths) == 0 { for _, plugin := range pluginSignaturePathMap {
mlog.Info("Found no files in plugins file store") reader, appErr := a.FileReader(plugin.path)
return nil
}
for _, path := range fileStorePaths {
if !strings.HasSuffix(path, ".tar.gz") {
mlog.Warn("Ignoring non-plugin in file store", mlog.String("bundle", path))
continue
}
var reader filesstore.ReadCloseSeeker
reader, appErr = a.FileReader(path)
if appErr != nil { if appErr != nil {
mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", path), mlog.Err(appErr)) mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr))
continue continue
} }
defer reader.Close() defer reader.Close()
mlog.Info("Syncing plugin from file store", mlog.String("bundle", path)) var signature filesstore.ReadCloseSeeker
if _, err := a.installPluginLocally(reader, true); err != nil { if *a.Config().PluginSettings.RequirePluginSignature {
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", path), mlog.Err(err)) signature, appErr = a.FileReader(plugin.signaturePath)
if appErr != nil {
mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr))
continue
} }
defer signature.Close()
} }
mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path))
if _, err := a.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil {
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(err))
}
}
return nil return nil
} }
@@ -364,7 +376,7 @@ func (a *App) DisablePlugin(id string) *model.AppError {
} }
if manifest == nil { if manifest == nil {
return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound)
} }
a.UpdateConfig(func(cfg *model.Config) { a.UpdateConfig(func(cfg *model.Config) {
@@ -410,6 +422,24 @@ func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
return resp, nil return resp, nil
} }
// GetMarketplacePlugin returns plugin from marketplace-server
func (a *App) GetMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.BaseMarketplacePlugin, *model.AppError) {
marketplaceClient, err := marketplace.NewClient(
*a.Config().PluginSettings.MarketplaceUrl,
a.HTTPService,
)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_client.app_error", nil, err.Error(), http.StatusInternalServerError)
}
filter := &model.MarketplacePluginFilter{Filter: request.Id}
plugin, err := marketplaceClient.GetPlugin(filter, request.Version)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_plugins.not_found.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return plugin, nil
}
// GetMarketplacePlugins returns a list of plugins from the marketplace-server, // GetMarketplacePlugins returns a list of plugins from the marketplace-server,
// and plugins that are installed locally. // and plugins that are installed locally.
func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*model.MarketplacePlugin, *model.AppError) { func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*model.MarketplacePlugin, *model.AppError) {
@@ -557,3 +587,33 @@ func (a *App) notifyPluginEnabled(manifest *model.Manifest) error {
return nil return nil
} }
func (a *App) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) {
fileStorePaths, appErr := a.ListDirectory(fileStorePluginFolder)
if appErr != nil {
return nil, model.NewAppError("getPluginsFromDir", "app.plugin.sync.list_filestore.app_error", nil, appErr.Error(), http.StatusInternalServerError)
}
pluginSignaturePathMap := make(map[string]*pluginSignaturePath)
for _, path := range fileStorePaths {
if strings.HasSuffix(path, ".tar.gz") {
id := strings.TrimSuffix(filepath.Base(path), ".tar.gz")
helper := &pluginSignaturePath{
pluginId: id,
path: path,
signaturePath: "",
}
pluginSignaturePathMap[id] = helper
}
}
for _, path := range fileStorePaths {
if strings.HasSuffix(path, ".sig") {
id := strings.TrimSuffix(filepath.Base(path), ".sig")
if val, ok := pluginSignaturePathMap[id]; !ok {
mlog.Error("Unknown signature", mlog.String("path", path))
} else {
val.signaturePath = path
}
}
}
return pluginSignaturePathMap, nil
}

Просмотреть файл

@@ -31,8 +31,8 @@
// Finally, in addition to managed plugins, note that there are unmanaged and prepackaged plugins. // Finally, in addition to managed plugins, note that there are unmanaged and prepackaged plugins.
// Unmanaged plugins are plugins installed manually to the configured local directory (PluginSettings.Directory). // Unmanaged plugins are plugins installed manually to the configured local directory (PluginSettings.Directory).
// Prepackaged plugins are included with the server. They otherwise follow the above flow, except do not get uploaded // Prepackaged plugins are included with the server. They otherwise follow the above flow, except do not get uploaded
// to the filestore. Prepackaged plugins override all other plugins with the same plugin id. Managed plugins // to the filestore. Prepackaged plugins override all other plugins with the same plugin id, but only when the prepackaged
// override unmanaged plugins with the same plugin id. // plugin is newer. Managed plugins unconditionally override unmanaged plugins with the same plugin id.
// //
package app package app
@@ -44,9 +44,11 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/blang/semver"
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/plugin" "github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/services/filesstore"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
) )
@@ -60,16 +62,38 @@ const fileStorePluginFolder = "plugins"
func (a *App) InstallPluginFromData(data model.PluginEventData) { func (a *App) InstallPluginFromData(data model.PluginEventData) {
mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id)) mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id))
fileStorePath := a.getBundleStorePath(data.Id) pluginSignaturePathMap, appErr := a.getPluginsFromFolder()
reader, appErr := a.FileReader(fileStorePath)
if appErr != nil { if appErr != nil {
mlog.Error("Failed to open plugin bundle from filestore.", mlog.String("path", fileStorePath), mlog.Err(appErr)) mlog.Error("Failed to get plugin signatures from filestore. Can't install plugin from data.", mlog.Err(appErr))
return
}
plugin, ok := pluginSignaturePathMap[data.Id]
if !ok {
mlog.Error("Failed to get plugin signature from filestore. Can't install plugin from data.", mlog.String("plugin id", data.Id))
return
}
reader, appErr := a.FileReader(plugin.path)
if appErr != nil {
mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr))
return
} }
defer reader.Close() defer reader.Close()
manifest, appErr := a.installPluginLocally(reader, true) var signature filesstore.ReadCloseSeeker
if *a.Config().PluginSettings.RequirePluginSignature {
signature, appErr = a.FileReader(plugin.signaturePath)
if appErr != nil { if appErr != nil {
mlog.Error("Failed to unpack plugin from filestore", mlog.Err(appErr), mlog.String("path", fileStorePath)) mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr))
return
}
defer signature.Close()
}
manifest, appErr := a.installPluginLocally(reader, signature, installPluginLocallyAlways)
if appErr != nil {
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(appErr))
return
} }
if err := a.notifyPluginEnabled(manifest); err != nil { if err := a.notifyPluginEnabled(manifest); err != nil {
@@ -93,20 +117,36 @@ func (a *App) RemovePluginFromData(data model.PluginEventData) {
} }
} }
// InstallPlugin unpacks and installs a plugin but does not enable or activate it. // InstallPluginWithSignature verifies and installs plugin.
func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) { func (a *App) InstallPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) {
return a.installPlugin(pluginFile, replace) return a.installPlugin(pluginFile, signature, installPluginLocallyAlways)
} }
func (a *App) installPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) { // InstallPlugin unpacks and installs a plugin but does not enable or activate it.
manifest, appErr := a.installPluginLocally(pluginFile, replace) func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) {
installationStrategy := installPluginLocallyOnlyIfNew
if replace {
installationStrategy = installPluginLocallyAlways
}
return a.installPlugin(pluginFile, nil, installationStrategy)
}
func (a *App) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
manifest, appErr := a.installPluginLocally(pluginFile, signature, installationStrategy)
if appErr != nil { if appErr != nil {
return nil, appErr return nil, appErr
} }
if signature != nil {
signature.Seek(0, 0)
if _, appErr = a.WriteFile(signature, a.getSignatureStorePath(manifest.Id)); appErr != nil {
return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, appErr.Error(), http.StatusInternalServerError)
}
}
// Store bundle in the file store to allow access from other servers. // Store bundle in the file store to allow access from other servers.
pluginFile.Seek(0, 0) pluginFile.Seek(0, 0)
if _, appErr := a.WriteFile(pluginFile, a.getBundleStorePath(manifest.Id)); appErr != nil { if _, appErr := a.WriteFile(pluginFile, a.getBundleStorePath(manifest.Id)); appErr != nil {
return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, appErr.Error(), http.StatusInternalServerError) return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, appErr.Error(), http.StatusInternalServerError)
} }
@@ -129,11 +169,28 @@ func (a *App) installPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Mani
return manifest, nil return manifest, nil
} }
func (a *App) installPluginLocally(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) { type pluginInstallationStrategy int
const (
// installPluginLocallyOnlyIfNew installs the given plugin locally only if no plugin with the same id has been unpacked.
installPluginLocallyOnlyIfNew pluginInstallationStrategy = iota
// installPluginLocallyOnlyIfNewOrUpgrade installs the given plugin locally only if no plugin with the same id has been unpacked, or if such a plugin is older.
installPluginLocallyOnlyIfNewOrUpgrade
// installPluginLocallyAlways unconditionally installs the given plugin locally only, clobbering any existing plugin with the same id.
installPluginLocallyAlways
)
func (a *App) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
pluginsEnvironment := a.GetPluginsEnvironment() pluginsEnvironment := a.GetPluginsEnvironment()
if pluginsEnvironment == nil { if pluginsEnvironment == nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
} }
// verify signature
if signature != nil {
if err := a.VerifyPlugin(pluginFile, signature); err != nil {
return nil, err
}
}
tmpDir, err := ioutil.TempDir("", "plugintmp") tmpDir, err := ioutil.TempDir("", "plugintmp")
if err != nil { if err != nil {
@@ -141,6 +198,7 @@ func (a *App) installPluginLocally(pluginFile io.ReadSeeker, replace bool) (*mod
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
pluginFile.Seek(0, 0)
if err = utils.ExtractTarGz(pluginFile, tmpDir); err != nil { if err = utils.ExtractTarGz(pluginFile, tmpDir); err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest) return nil, model.NewAppError("installPluginLocally", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest)
} }
@@ -169,16 +227,45 @@ func (a *App) installPluginLocally(pluginFile io.ReadSeeker, replace bool) (*mod
return nil, model.NewAppError("installPluginLocally", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError) return nil, model.NewAppError("installPluginLocally", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
// Check that there is no plugin with the same ID // Check for plugins installed with the same ID.
var existingManifest *model.Manifest
for _, bundle := range bundles { for _, bundle := range bundles {
if bundle.Manifest != nil && bundle.Manifest.Id == manifest.Id { if bundle.Manifest != nil && bundle.Manifest.Id == manifest.Id {
if !replace { existingManifest = bundle.Manifest
break
}
}
if existingManifest != nil {
// Return an error if already installed and strategy disallows installation.
if installationStrategy == installPluginLocallyOnlyIfNew {
return nil, model.NewAppError("installPluginLocally", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest) return nil, model.NewAppError("installPluginLocally", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest)
} }
if err := a.removePluginLocally(manifest.Id); err != nil { // Skip installation if already installed and newer.
return nil, model.NewAppError("installPluginLocally", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest) if installationStrategy == installPluginLocallyOnlyIfNewOrUpgrade {
var version, existingVersion semver.Version
version, err = semver.Parse(manifest.Version)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
} }
existingVersion, err = semver.Parse(existingManifest.Version)
if err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
}
if version.LTE(existingVersion) {
mlog.Debug("Skipping local installation of plugin since existing version is newer", mlog.String("plugin_id", manifest.Id))
return nil, nil
}
}
// Otherwise remove the existing installation prior to install below.
mlog.Debug("Removing existing installation of plugin before local install", mlog.String("plugin_id", existingManifest.Id), mlog.String("version", existingManifest.Version))
if err := a.removePluginLocally(existingManifest.Id); err != nil {
return nil, model.NewAppError("installPluginLocally", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest)
} }
} }
@@ -240,9 +327,12 @@ func (a *App) removePlugin(id string) *model.AppError {
if !bundleExist { if !bundleExist {
return nil return nil
} }
if err := a.RemoveFile(storePluginFileName); err != nil { if err = a.RemoveFile(storePluginFileName); err != nil {
return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
if err = a.removeSignature(id); err != nil {
mlog.Error("Can't remove signature", mlog.Err(err))
}
a.notifyClusterPluginEvent( a.notifyClusterPluginEvent(
model.CLUSTER_EVENT_REMOVE_PLUGIN, model.CLUSTER_EVENT_REMOVE_PLUGIN,
@@ -280,7 +370,7 @@ func (a *App) removePluginLocally(id string) *model.AppError {
} }
if manifest == nil { if manifest == nil {
return model.NewAppError("removePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) return model.NewAppError("removePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound)
} }
pluginsEnvironment.Deactivate(id) pluginsEnvironment.Deactivate(id)
@@ -294,6 +384,26 @@ func (a *App) removePluginLocally(id string) *model.AppError {
return nil return nil
} }
func (a *App) removeSignature(pluginId string) *model.AppError {
filePath := a.getSignatureStorePath(pluginId)
exists, err := a.FileExists(filePath)
if err != nil {
return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if !exists {
mlog.Debug("no plugin signature to remove", mlog.String("plugin_id", pluginId))
return nil
}
if err = a.RemoveFile(filePath); err != nil {
return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return nil
}
func (a *App) getBundleStorePath(id string) string { func (a *App) getBundleStorePath(id string) string {
return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.tar.gz", id)) return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.tar.gz", id))
} }
func (a *App) getSignatureStorePath(id string) string {
return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.sig", id))
}

258
app/plugin_install_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,258 @@
package app
import (
"archive/tar"
"bytes"
"compress/gzip"
"io"
"sort"
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type nilReadSeeker struct {
}
func (r *nilReadSeeker) Read(p []byte) (int, error) {
return 0, io.EOF
}
func (r *nilReadSeeker) Seek(offset int64, whence int) (int64, error) {
return 0, nil
}
type testFile struct {
Name, Body string
}
func makeInMemoryGzipTarFile(t *testing.T, files []testFile) *bytes.Reader {
var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
tgz := tar.NewWriter(gzWriter)
for _, file := range files {
hdr := &tar.Header{
Name: file.Name,
Mode: 0600,
Size: int64(len(file.Body)),
}
err := tgz.WriteHeader(hdr)
require.NoError(t, err, "failed to write %s to in-memory tar file", file.Name)
_, err = tgz.Write([]byte(file.Body))
require.NoError(t, err, "failed to write body of %s to in-memory tar file", file.Name)
}
err := tgz.Close()
require.NoError(t, err, "failed to close in-memory tar file")
err = gzWriter.Close()
require.NoError(t, err, "failed to close in-memory tar.gz file")
return bytes.NewReader(buf.Bytes())
}
type byBundleInfoId []*model.BundleInfo
func (b byBundleInfoId) Len() int { return len(b) }
func (b byBundleInfoId) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byBundleInfoId) Less(i, j int) bool { return b[i].Manifest.Id < b[j].Manifest.Id }
func TestInstallPluginLocally(t *testing.T) {
t.Run("invalid tar", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
actualManifest, appErr := th.App.installPluginLocally(&nilReadSeeker{}, nil, installPluginLocallyOnlyIfNew)
require.NotNil(t, appErr)
assert.Equal(t, "app.plugin.extract.app_error", appErr.Id, appErr.Error())
require.Nil(t, actualManifest)
})
t.Run("missing manifest", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
reader := makeInMemoryGzipTarFile(t, []testFile{
{"test", "test file"},
})
actualManifest, appErr := th.App.installPluginLocally(reader, nil, installPluginLocallyOnlyIfNew)
require.NotNil(t, appErr)
assert.Equal(t, "app.plugin.manifest.app_error", appErr.Id, appErr.Error())
require.Nil(t, actualManifest)
})
installPlugin := func(t *testing.T, th *TestHelper, id, version string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
t.Helper()
manifest := &model.Manifest{
Id: id,
Version: version,
}
reader := makeInMemoryGzipTarFile(t, []testFile{
{"plugin.json", manifest.ToJson()},
})
actualManifest, appError := th.App.installPluginLocally(reader, nil, installationStrategy)
if actualManifest != nil {
require.Equal(t, manifest, actualManifest)
}
return actualManifest, appError
}
t.Run("invalid plugin id", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
actualManifest, appErr := installPlugin(t, th, "invalid#plugin#id", "version", installPluginLocallyOnlyIfNew)
require.NotNil(t, appErr)
assert.Equal(t, "app.plugin.invalid_id.app_error", appErr.Id, appErr.Error())
require.Nil(t, actualManifest)
})
// The following tests fail mysteriously on CI due to an unexpected bundle being present.
// This exists to clean up manually until we figure out what test isn't cleaning up after
// itself.
cleanExistingBundles := func(t *testing.T, th *TestHelper) {
pluginsEnvironment := th.App.GetPluginsEnvironment()
require.NotNil(t, pluginsEnvironment)
bundleInfos, err := pluginsEnvironment.Available()
require.Nil(t, err)
for _, bundleInfo := range bundleInfos {
err := th.App.removePluginLocally(bundleInfo.Manifest.Id)
require.Nilf(t, err, "failed to remove existing plugin %s", bundleInfo.Manifest.Id)
}
}
assertBundleInfoManifests := func(t *testing.T, th *TestHelper, manifests []*model.Manifest) {
pluginsEnvironment := th.App.GetPluginsEnvironment()
require.NotNil(t, pluginsEnvironment)
bundleInfos, err := pluginsEnvironment.Available()
require.Nil(t, err)
sort.Sort(byBundleInfoId(bundleInfos))
actualManifests := make([]*model.Manifest, 0, len(bundleInfos))
for _, bundleInfo := range bundleInfos {
actualManifests = append(actualManifests, bundleInfo.Manifest)
}
require.Equal(t, manifests, actualManifests)
}
t.Run("no plugins already installed", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
cleanExistingBundles(t, th)
manifest, appErr := installPlugin(t, th, "valid", "0.0.1", installPluginLocallyOnlyIfNew)
require.Nil(t, appErr)
require.NotNil(t, manifest)
assertBundleInfoManifests(t, th, []*model.Manifest{manifest})
})
t.Run("different plugin already installed", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
cleanExistingBundles(t, th)
otherManifest, appErr := installPlugin(t, th, "other", "0.0.1", installPluginLocallyOnlyIfNew)
require.Nil(t, appErr)
require.NotNil(t, otherManifest)
manifest, appErr := installPlugin(t, th, "valid", "0.0.1", installPluginLocallyOnlyIfNew)
require.Nil(t, appErr)
require.NotNil(t, manifest)
assertBundleInfoManifests(t, th, []*model.Manifest{otherManifest, manifest})
})
t.Run("same plugin already installed", func(t *testing.T) {
t.Run("install only if new", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
cleanExistingBundles(t, th)
existingManifest, appErr := installPlugin(t, th, "valid", "0.0.1", installPluginLocallyOnlyIfNew)
require.Nil(t, appErr)
require.NotNil(t, existingManifest)
manifest, appErr := installPlugin(t, th, "valid", "0.0.1", installPluginLocallyOnlyIfNew)
require.NotNil(t, appErr)
require.Equal(t, "app.plugin.install_id.app_error", appErr.Id, appErr.Error())
require.Nil(t, manifest)
assertBundleInfoManifests(t, th, []*model.Manifest{existingManifest})
})
t.Run("install if upgrade, but older", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
cleanExistingBundles(t, th)
existingManifest, appErr := installPlugin(t, th, "valid", "0.0.2", installPluginLocallyOnlyIfNewOrUpgrade)
require.Nil(t, appErr)
require.NotNil(t, existingManifest)
manifest, appErr := installPlugin(t, th, "valid", "0.0.1", installPluginLocallyOnlyIfNewOrUpgrade)
require.Nil(t, appErr)
require.Nil(t, manifest)
assertBundleInfoManifests(t, th, []*model.Manifest{existingManifest})
})
t.Run("install if upgrade, but same version", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
cleanExistingBundles(t, th)
existingManifest, appErr := installPlugin(t, th, "valid", "0.0.2", installPluginLocallyOnlyIfNewOrUpgrade)
require.Nil(t, appErr)
require.NotNil(t, existingManifest)
manifest, appErr := installPlugin(t, th, "valid", "0.0.2", installPluginLocallyOnlyIfNewOrUpgrade)
require.Nil(t, appErr)
require.Nil(t, manifest)
assertBundleInfoManifests(t, th, []*model.Manifest{existingManifest})
})
t.Run("install if upgrade, newer version", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
cleanExistingBundles(t, th)
existingManifest, appErr := installPlugin(t, th, "valid", "0.0.2", installPluginLocallyOnlyIfNewOrUpgrade)
require.Nil(t, appErr)
require.NotNil(t, existingManifest)
manifest, appErr := installPlugin(t, th, "valid", "0.0.3", installPluginLocallyOnlyIfNewOrUpgrade)
require.Nil(t, appErr)
require.NotNil(t, manifest)
assertBundleInfoManifests(t, th, []*model.Manifest{manifest})
})
t.Run("install always, old version", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
cleanExistingBundles(t, th)
existingManifest, appErr := installPlugin(t, th, "valid", "0.0.2", installPluginLocallyAlways)
require.Nil(t, appErr)
require.NotNil(t, existingManifest)
manifest, appErr := installPlugin(t, th, "valid", "0.0.1", installPluginLocallyAlways)
require.Nil(t, appErr)
require.NotNil(t, manifest)
assertBundleInfoManifests(t, th, []*model.Manifest{manifest})
})
})
}

46
app/plugin_public_keys.go Обычный файл
Просмотреть файл

@@ -0,0 +1,46 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
var mattermostPluginPublicKey []byte = []byte(`-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBF2gen8BDADKQObdPa6PagvYYMHNGIswCU9mVjOxr5g6niGQ/AxMW7AaHpkk
16/oAzJ+DSyJRRgJMlFbN0iKBrZ6pi1pO5eS4l1CWW3eATr+32gW40SuS/sgzVrS
OYPocqtsC9XWHK2j/UFbaI7aivnUYKIuBzhAWdcUYggjd1qgmM18zWYkuV1Jnywu
Xue3Vsc/pLGqybG/EkBmZHRktr4fNn2xEjmKnUKp28vMF4Pz5e8/2qklSsc9UVl5
avkex+glOeJSWF3L7S5CmHAWVgQNwKoJrvq7pKOUsZqrHScjyujeKp1Y6cUZdcBF
8bsF1I+J2RxQFqcC6O08x29948P8UkOv4/FpUGYhx6tcqnQ0PdT4fjPslRapvdZo
RiGdlvJLKUvhfRF0cgPxflde7M42cV5saOXKyaF2hPJi/SsFkTVSnyCyixdr8z+M
QMIcgtrQ26ig9s5J0h2j3y9sgvvTh1nxE/XWOlrXjCVohNSRWZBjX1PEd2dAk4ZK
OEB5YcST61kbhd0AEQEAAbRBTWF0dGVybW9zdCBJbmMuIChQbHVnaW4gc2lnbmlu
ZyBSU0EgZGV2LWtleSkgPGFsaUBtYXR0ZXJtb3N0LmNvbT6JAc4EEwEIADgWIQTz
+s5F4N5kLIvWqOZMfGViwZLMHwUCXaB6fwIbAwULCQgHAgYVCgkICwIEFgIDAQIe
AQIXgAAKCRBMfGViwZLMH/NoC/0UAvpTvT1sBD6qFpUOPZUmUSLLndtLzuYoMqID
0vvTdxb1PdbQpVX2sMuS19upyAmkVRh50uxGcsOLU/lUaF8C1C22zeGvtdkbw+79
Gv1AYlyCCEanhQSdH4z/t8W8nBcSw8kA+423guSzlIrrRSPCIyHSTP/MlwituN1+
wEUlXMXnjY4nNpyik+e9LoKK05zCy1mYswAnx1I5IH44iOfjqjz2FGv3iFhuc5rt
cEC26RyYCNVH7mIcCwd25/Np+IQbftfUVEugr1OGsSvdbAA3qWRtC9Q7VcFXy3A8
1svxkGPiZw60oxkG9V5v1l/ETCWztzvZvXXXZcWNaDb81rpn2LFeFulJKBxLLonC
gR/8l1hJAt8uS8ymOQRpVK9QVztlyxtZWZ7FxsfsC4AXthU3VFZjLUd1Tf4k5eW2
ov9JPjcSHHBQp6ScjtSgLTb4s2B5mD7VFBhuFOTWs1mbpVaRVIpguvYKIdxtDfek
0bjPQSI62K9G8mKGE4SqibfXhhO5AY0EXaB6fwEMALrPejAgOh7IWxmWJPO++8Fv
8eJD5nU7I3I4cWgJolXDSP4gEpkwlfHzAn7BJwTKTvZ5oDqpQCQV3mwqumQlRBKS
DHXU3b1Z4MOq3SbQlFfNduTCzKa7a79/DFf96TXilpVW/XT3HdN69810oCfo87Ub
/fx2G6h9JLaxdwJ57b/8Ej4eNbclGgE4GYHP9Xf0FX7F2xIqaIm/RCTGf7uGlaU0
RmeEFmy69T7jUAGI7g1gN1eldQ0F1q2HPuhP4iP39ZAz9K4Oyzl+B2IcHXyH2MjP
WXfgjVi87O5rEUvA/cpYU5WFc8hflP7cil16rb/PiALzEx+GCpdARxvtMT/IbK/3
luC2l/uw2ZYwtaL+8e9vyDOkVaWTD408Q51qrIANWwwLUSn71TuImGxCDzeuN79V
/T5PSjR5o/s6lR0CGzNL/B3MziuD2Vr5Wl1LYkJfTlgmGrnm6aJ/zKbrOMnkeAu5
Q0VgVOyibKhTu31WdXJ/jbhPQ5yd4UkduSAODStsRQARAQABiQG2BBgBCAAgFiEE
8/rOReDeZCyL1qjmTHxlYsGSzB8FAl2gen8CGwwACgkQTHxlYsGSzB8v7Av+IC9I
t7U3W51hCXH2wNcaSi8hxSYpFMl7GMX9zSKE8nKDmKBXUV7RJtU3cpGiGvgl+LLw
qBtjahRP+PU8AQSLL/4W97ldQrrdnOET6mtEiJylliA187SkimSixyy31YnUKDn6
PIeapJaoJ+JI22VhqbGd5tJCDbjTRFyiJP0L6vCEUAoLhpaqsqUiUw86//USl3uh
P+G9m2z3QPmxVFP/xZFEbihprpe/AccDLFjTwEWAMag6vV0NoI0E+JGeICKtzkxB
Pgi71N/jHKULPVMPXkaD30GT4k72lmuwqfvLz9uEhgSeAHakma8wUlp0aSw+kk4a
dZmqpBXcl6VFSDpCJXANUS4IUqjVqnK4nAGONR4JFaoejtAnmlz61EtjWuzPYjQS
0dL1Jv69WXLal7tzTJOZLekHas8DxzMgkID4IXCaSjwDb34mVgdaWyD1E302U3eX
IfS6J8Zp6Bs1baubHXFifXU6SV805b6i46/1m99OPsVH85zCUHvu4asaiLcR
=qIjw
-----END PGP PUBLIC KEY BLOCK-----`)

132
app/plugin_signature.go Обычный файл
Просмотреть файл

@@ -0,0 +1,132 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"path/filepath"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
)
// GetPluginPublicKeyFiles returns all public keys listed in the config.
func (a *App) GetPluginPublicKeyFiles() ([]string, *model.AppError) {
return a.Config().PluginSettings.SignaturePublicKeyFiles, nil
}
// GetPublicKey will return the actual public key saved in the `name` file.
func (a *App) GetPublicKey(name string) ([]byte, *model.AppError) {
data, err := a.Srv.configStore.GetFile(name)
if err != nil {
return nil, model.NewAppError("GetPublicKey", "app.plugin.get_public_key.get_file.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return data, nil
}
// AddPublicKey will add plugin public key to the config. Overwrites the previous file
func (a *App) AddPublicKey(name string, key io.Reader) *model.AppError {
if model.IsSamlFile(&a.Config().SamlSettings, name) {
return model.NewAppError("AddPublicKey", "app.plugin.modify_saml.app_error", nil, "", http.StatusInternalServerError)
}
data, err := ioutil.ReadAll(key)
if err != nil {
return model.NewAppError("AddPublicKey", "app.plugin.write_file.read.app_error", nil, err.Error(), http.StatusInternalServerError)
}
err = a.Srv.configStore.SetFile(name, data)
if err != nil {
return model.NewAppError("AddPublicKey", "app.plugin.write_file.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
}
a.UpdateConfig(func(cfg *model.Config) {
if !utils.StringInSlice(name, cfg.PluginSettings.SignaturePublicKeyFiles) {
cfg.PluginSettings.SignaturePublicKeyFiles = append(cfg.PluginSettings.SignaturePublicKeyFiles, name)
}
})
return nil
}
// DeletePublicKey will delete plugin public key from the config.
func (a *App) DeletePublicKey(name string) *model.AppError {
if model.IsSamlFile(&a.Config().SamlSettings, name) {
return model.NewAppError("AddPublicKey", "app.plugin.modify_saml.app_error", nil, "", http.StatusInternalServerError)
}
filename := filepath.Base(name)
if err := a.Srv.configStore.RemoveFile(filename); err != nil {
return model.NewAppError("DeletePublicKey", "app.plugin.delete_public_key.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
}
a.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.SignaturePublicKeyFiles = utils.RemoveStringFromSlice(filename, cfg.PluginSettings.SignaturePublicKeyFiles)
})
return nil
}
// VerifyPlugin checks that the given signature corresponds to the given plugin and matches a trusted certificate.
func (a *App) VerifyPlugin(plugin, signature io.ReadSeeker) *model.AppError {
if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil {
return nil
}
publicKeys, appErr := a.GetPluginPublicKeyFiles()
if appErr != nil {
return appErr
}
for _, pk := range publicKeys {
pkBytes, appErr := a.GetPublicKey(pk)
if appErr != nil {
mlog.Error("Unable to get public key for ", mlog.String("filename", pk))
continue
}
publicKey := bytes.NewReader(pkBytes)
plugin.Seek(0, 0)
signature.Seek(0, 0)
if err := verifySignature(publicKey, plugin, signature); err == nil {
return nil
}
}
return model.NewAppError("VerifyPlugin", "api.plugin.verify_plugin.app_error", nil, "", http.StatusInternalServerError)
}
func verifySignature(publicKey, message, signatrue io.Reader) error {
pk, err := decodeIfArmored(publicKey)
if err != nil {
return errors.Wrap(err, "can't decode public key")
}
s, err := decodeIfArmored(signatrue)
if err != nil {
return errors.Wrap(err, "can't decode signature")
}
return verifyBinarySignature(pk, message, s)
}
func verifyBinarySignature(publicKey, signedFile, signature io.Reader) error {
keyring, err := openpgp.ReadKeyRing(publicKey)
if err != nil {
return errors.Wrap(err, "can't read public key")
}
if _, err = openpgp.CheckDetachedSignature(keyring, signedFile, signature); err != nil {
return errors.Wrap(err, "error while checking the signature")
}
return nil
}
func decodeIfArmored(reader io.Reader) (io.Reader, error) {
readBytes, err := ioutil.ReadAll(reader)
if err != nil {
return nil, errors.Wrap(err, "can't read the file")
}
block, err := armor.Decode(bytes.NewReader(readBytes))
if err != nil {
return bytes.NewReader(readBytes), nil
}
return block.Body, nil
}

102
app/plugin_signature_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,102 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/mattermost/mattermost-server/utils/fileutils"
"github.com/stretchr/testify/require"
)
func TestPluginPublicKeys(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
path, _ := fileutils.FindDir("tests")
publicKeyFilename := "test-public-key.plugin.gpg"
publicKey, err := ioutil.ReadFile(filepath.Join(path, publicKeyFilename))
require.Nil(t, err)
fileReader, err := os.Open(filepath.Join(path, publicKeyFilename))
require.Nil(t, err)
defer fileReader.Close()
th.App.AddPublicKey(publicKeyFilename, fileReader)
file, err := th.App.GetPublicKey(publicKeyFilename)
require.Nil(t, err)
require.Equal(t, publicKey, file)
_, err = th.App.GetPublicKey("wrong file name")
require.NotNil(t, err)
_, err = th.App.GetPublicKey("wrong-file-name.plugin.gpg")
require.NotNil(t, err)
err = th.App.DeletePublicKey("wrong file name")
require.Nil(t, err)
err = th.App.DeletePublicKey("wrong-file-name.plugin.gpg")
require.Nil(t, err)
err = th.App.DeletePublicKey(publicKeyFilename)
require.Nil(t, err)
_, err = th.App.GetPublicKey(publicKeyFilename)
require.NotNil(t, err)
}
func TestVerifySignature(t *testing.T) {
path, _ := fileutils.FindDir("tests")
pluginFilename := "testplugin.tar.gz"
signatureFilename := "testplugin.tar.gz.sig"
armoredSignatureFilename := "testplugin.tar.gz.asc"
publicKeyFilename := "development-public-key.gpg"
armoredPublicKeyFilename := "development-public-key.asc"
t.Run("verify armored signature and armored public key", func(t *testing.T) {
publicKeyFileReader, err := os.Open(filepath.Join(path, armoredPublicKeyFilename))
require.Nil(t, err)
defer publicKeyFileReader.Close()
pluginFileReader, err := os.Open(filepath.Join(path, pluginFilename))
require.Nil(t, err)
defer pluginFileReader.Close()
signatureFileReader, err := os.Open(filepath.Join(path, armoredSignatureFilename))
require.Nil(t, err)
defer signatureFileReader.Close()
require.Nil(t, verifySignature(publicKeyFileReader, pluginFileReader, signatureFileReader))
})
t.Run("verify non armored signature and armored public key", func(t *testing.T) {
publicKeyFileReader, err := os.Open(filepath.Join(path, armoredPublicKeyFilename))
require.Nil(t, err)
defer publicKeyFileReader.Close()
pluginFileReader, err := os.Open(filepath.Join(path, pluginFilename))
require.Nil(t, err)
defer pluginFileReader.Close()
signatureFileReader, err := os.Open(filepath.Join(path, signatureFilename))
require.Nil(t, err)
defer signatureFileReader.Close()
require.Nil(t, verifySignature(publicKeyFileReader, pluginFileReader, signatureFileReader))
})
t.Run("verify armored signature and non armored public key", func(t *testing.T) {
publicKeyFileReader, err := os.Open(filepath.Join(path, publicKeyFilename))
require.Nil(t, err)
defer publicKeyFileReader.Close()
pluginFileReader, err := os.Open(filepath.Join(path, pluginFilename))
require.Nil(t, err)
defer pluginFileReader.Close()
armoredSignatureFileReader, err := os.Open(filepath.Join(path, armoredSignatureFilename))
require.Nil(t, err)
defer armoredSignatureFileReader.Close()
require.Nil(t, verifySignature(publicKeyFileReader, pluginFileReader, armoredSignatureFileReader))
})
t.Run("verify non armored signature and non armored public key", func(t *testing.T) {
publicKeyFileReader, err := os.Open(filepath.Join(path, publicKeyFilename))
require.Nil(t, err)
defer publicKeyFileReader.Close()
pluginFileReader, err := os.Open(filepath.Join(path, pluginFilename))
require.Nil(t, err)
defer pluginFileReader.Close()
signatureFileReader, err := os.Open(filepath.Join(path, signatureFilename))
require.Nil(t, err)
defer signatureFileReader.Close()
require.Nil(t, verifySignature(publicKeyFileReader, pluginFileReader, signatureFileReader))
})
}

Просмотреть файл

@@ -483,7 +483,7 @@ func TestPluginSync(t *testing.T) {
s3Port := os.Getenv("CI_MINIO_PORT") s3Port := os.Getenv("CI_MINIO_PORT")
if s3Port == "" { if s3Port == "" {
s3Port = "9001" s3Port = "9000"
} }
s3Endpoint := fmt.Sprintf("%s:%s", s3Host, s3Port) s3Endpoint := fmt.Sprintf("%s:%s", s3Host, s3Port)
@@ -508,6 +508,7 @@ func TestPluginSync(t *testing.T) {
*cfg.PluginSettings.Enable = true *cfg.PluginSettings.Enable = true
*cfg.PluginSettings.Directory = "./test-plugins" *cfg.PluginSettings.Directory = "./test-plugins"
*cfg.PluginSettings.ClientDirectory = "./test-client-plugins" *cfg.PluginSettings.ClientDirectory = "./test-client-plugins"
*cfg.PluginSettings.RequirePluginSignature = false
}) })
th.App.UpdateConfig(testCase.ConfigFunc) th.App.UpdateConfig(testCase.ConfigFunc)
@@ -530,7 +531,7 @@ func TestPluginSync(t *testing.T) {
// Check if installed // Check if installed
pluginStatus, err := env.Statuses() pluginStatus, err := env.Statuses()
require.Nil(t, err) require.Nil(t, err)
require.True(t, len(pluginStatus) == 1) require.Len(t, pluginStatus, 1)
require.Equal(t, pluginStatus[0].PluginId, "testplugin") require.Equal(t, pluginStatus[0].PluginId, "testplugin")
// Bundle removed from the file store case // Bundle removed from the file store case
@@ -543,7 +544,54 @@ func TestPluginSync(t *testing.T) {
// Check if removed // Check if removed
pluginStatus, err = env.Statuses() pluginStatus, err = env.Statuses()
require.Nil(t, err) require.Nil(t, err)
require.True(t, len(pluginStatus) == 0) require.Len(t, pluginStatus, 0)
// RequirePluginSignature = true case
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.RequirePluginSignature = true
})
pluginFileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
defer pluginFileReader.Close()
_, appErr = th.App.WriteFile(pluginFileReader, th.App.getBundleStorePath("testplugin.tar.gz"))
checkNoError(t, appErr)
// no signature
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
pluginStatus, err = env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 0)
// Wrong signature
signatureFileReader, err := os.Open(filepath.Join(path, "testpluginv2.tar.gz.sig"))
require.NoError(t, err)
defer signatureFileReader.Close()
filePath := fmt.Sprintf("%s.sig", th.App.getBundleStorePath("testplugin"))
_, appErr = th.App.WriteFile(signatureFileReader, filePath)
checkNoError(t, appErr)
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
pluginStatus, err = env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 0)
// Correct signature
signatureFileReader, err = os.Open(filepath.Join(path, "testplugin.tar.gz.sig"))
require.NoError(t, err)
defer signatureFileReader.Close()
filePath = fmt.Sprintf("%s.sig", th.App.getBundleStorePath("testplugin"))
_, appErr = th.App.WriteFile(signatureFileReader, filePath)
checkNoError(t, appErr)
appErr = th.App.SyncPlugins()
checkNoError(t, appErr)
pluginStatus, err = env.Statuses()
require.Nil(t, err)
require.Len(t, pluginStatus, 1)
require.Equal(t, pluginStatus[0].PluginId, "testplugin")
}) })
} }
} }

Просмотреть файл

@@ -261,6 +261,21 @@ func NewServer(options ...Option) (*Server, error) {
s.StartElasticsearch() s.StartElasticsearch()
} }
s.AddConfigListener(func(oldConfig *model.Config, newConfig *model.Config) {
if *oldConfig.GuestAccountsSettings.Enable && !*newConfig.GuestAccountsSettings.Enable {
if appErr := s.FakeApp().DeactivateGuests(); appErr != nil {
mlog.Error("Unable to deactivate guest accounts", mlog.Err(appErr))
}
}
})
// Disable active guest accounts on first run if guest accounts are disabled
if !*s.Config().GuestAccountsSettings.Enable {
if appErr := s.FakeApp().DeactivateGuests(); appErr != nil {
mlog.Error("Unable to deactivate guest accounts", mlog.Err(appErr))
}
}
s.initJobs() s.initJobs()
if s.runjobs { if s.runjobs {

Просмотреть файл

@@ -1333,10 +1333,19 @@ func (a *App) GetTeamIdFromQuery(query url.Values) (string, *model.AppError) {
} }
func (a *App) SanitizeTeam(session model.Session, team *model.Team) *model.Team { func (a *App) SanitizeTeam(session model.Session, team *model.Team) *model.Team {
if !a.SessionHasPermissionToTeam(session, team.Id, model.PERMISSION_MANAGE_TEAM) { if a.SessionHasPermissionToTeam(session, team.Id, model.PERMISSION_MANAGE_TEAM) {
team.Sanitize() return team
} }
if a.SessionHasPermissionToTeam(session, team.Id, model.PERMISSION_INVITE_USER) {
inviteId := team.InviteId
team.Sanitize()
team.InviteId = inviteId
return team
}
team.Sanitize()
return team return team
} }

Просмотреть файл

@@ -419,6 +419,7 @@ func TestSanitizeTeam(t *testing.T) {
team := &model.Team{ team := &model.Team{
Id: model.NewId(), Id: model.NewId(),
Email: th.MakeEmail(), Email: th.MakeEmail(),
InviteId: model.NewId(),
AllowedDomains: "example.com", AllowedDomains: "example.com",
} }
@@ -443,6 +444,7 @@ func TestSanitizeTeam(t *testing.T) {
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
require.Empty(t, sanitized.Email, "should've sanitized team") require.Empty(t, sanitized.Email, "should've sanitized team")
require.Empty(t, sanitized.InviteId, "should've sanitized inviteid")
}) })
t.Run("user of the team", func(t *testing.T) { t.Run("user of the team", func(t *testing.T) {
@@ -460,6 +462,7 @@ func TestSanitizeTeam(t *testing.T) {
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
require.Empty(t, sanitized.Email, "should've sanitized team") require.Empty(t, sanitized.Email, "should've sanitized team")
require.NotEmpty(t, sanitized.InviteId, "should have not sanitized inviteid")
}) })
t.Run("team admin", func(t *testing.T) { t.Run("team admin", func(t *testing.T) {
@@ -477,6 +480,7 @@ func TestSanitizeTeam(t *testing.T) {
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team") require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
require.NotEmpty(t, sanitized.InviteId, "shouldn't have sanitized inviteid")
}) })
t.Run("team admin of another team", func(t *testing.T) { t.Run("team admin of another team", func(t *testing.T) {
@@ -494,6 +498,7 @@ func TestSanitizeTeam(t *testing.T) {
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
require.Empty(t, sanitized.Email, "should've sanitized team") require.Empty(t, sanitized.Email, "should've sanitized team")
require.Empty(t, sanitized.InviteId, "should've sanitized inviteid")
}) })
t.Run("system admin, not a user of team", func(t *testing.T) { t.Run("system admin, not a user of team", func(t *testing.T) {
@@ -511,6 +516,7 @@ func TestSanitizeTeam(t *testing.T) {
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team") require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
require.NotEmpty(t, sanitized.InviteId, "shouldn't have sanitized inviteid")
}) })
t.Run("system admin, user of team", func(t *testing.T) { t.Run("system admin, user of team", func(t *testing.T) {
@@ -528,6 +534,7 @@ func TestSanitizeTeam(t *testing.T) {
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team") require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
require.NotEmpty(t, sanitized.InviteId, "shouldn't have sanitized inviteid")
}) })
} }

Просмотреть файл

@@ -943,28 +943,28 @@ func (a *App) UpdatePasswordAsUser(userId, currentPassword, newPassword string)
return a.UpdatePasswordSendEmail(user, newPassword, T("api.user.update_password.menu")) return a.UpdatePasswordSendEmail(user, newPassword, T("api.user.update_password.menu"))
} }
func (a *App) userDeactivated(user *model.User) *model.AppError { func (a *App) userDeactivated(userId string) *model.AppError {
if err := a.RevokeAllSessions(user.Id); err != nil { if err := a.RevokeAllSessions(userId); err != nil {
return err return err
} }
a.SetStatusOffline(user.Id, false) a.SetStatusOffline(userId, false)
if *a.Config().ServiceSettings.DisableBotsWhenOwnerIsDeactivated { if *a.Config().ServiceSettings.DisableBotsWhenOwnerIsDeactivated {
a.disableUserBots(user.Id) a.disableUserBots(userId)
} }
return nil return nil
} }
func (a *App) invalidateUserChannelMembersCaches(user *model.User) *model.AppError { func (a *App) invalidateUserChannelMembersCaches(userId string) *model.AppError {
teamsForUser, err := a.GetTeamsForUser(user.Id) teamsForUser, err := a.GetTeamsForUser(userId)
if err != nil { if err != nil {
return err return err
} }
for _, team := range teamsForUser { for _, team := range teamsForUser {
channelsForUser, err := a.GetChannelsForUser(team.Id, user.Id, false) channelsForUser, err := a.GetChannelsForUser(team.Id, userId, false)
if err != nil { if err != nil {
return err return err
} }
@@ -992,12 +992,12 @@ func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.A
ruser := userUpdate.New ruser := userUpdate.New
if !active { if !active {
if err := a.userDeactivated(ruser); err != nil { if err := a.userDeactivated(ruser.Id); err != nil {
return nil, err return nil, err
} }
} }
a.invalidateUserChannelMembersCaches(user) a.invalidateUserChannelMembersCaches(user.Id)
a.InvalidateCacheForUser(user.Id) a.InvalidateCacheForUser(user.Id)
a.sendUpdatedUserEvent(*ruser) a.sendUpdatedUserEvent(*ruser)
@@ -1005,6 +1005,27 @@ func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.A
return ruser, nil return ruser, nil
} }
func (a *App) DeactivateGuests() *model.AppError {
userIds, err := a.Srv.Store.User().DeactivateGuests()
if err != nil {
return err
}
for _, userId := range userIds {
if err := a.userDeactivated(userId); err != nil {
return err
}
}
a.Srv.Store.Channel().ClearCaches()
a.Srv.Store.User().ClearCaches()
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_GUESTS_DEACTIVATED, "", "", "", nil)
a.Publish(message)
return nil
}
func (a *App) GetSanitizeOptions(asAdmin bool) map[string]bool { func (a *App) GetSanitizeOptions(asAdmin bool) map[string]bool {
options := a.Config().GetSanitizeOptions() options := a.Config().GetSanitizeOptions()
if asAdmin { if asAdmin {

Просмотреть файл

@@ -1164,3 +1164,27 @@ func TestDemoteUserToGuest(t *testing.T) {
assert.Len(t, *channelMembers, 3) assert.Len(t, *channelMembers, 3)
}) })
} }
func TestDeactivateGuests(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
guest1 := th.CreateGuest()
guest2 := th.CreateGuest()
user := th.CreateUser()
err := th.App.DeactivateGuests()
require.Nil(t, err)
guest1, err = th.App.GetUser(guest1.Id)
assert.Nil(t, err)
assert.NotEqual(t, int64(0), guest1.DeleteAt)
guest2, err = th.App.GetUser(guest2.Id)
assert.Nil(t, err)
assert.NotEqual(t, int64(0), guest2.DeleteAt)
user, err = th.App.GetUser(user.Id)
assert.Nil(t, err)
assert.Equal(t, int64(0), user.DeleteAt)
}

Просмотреть файл

@@ -100,6 +100,8 @@ pipeline {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
cd /go/src/github.com/mattermost/mattermost-server cd /go/src/github.com/mattermost/mattermost-server
echo "Installing golangci-lint"
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b /usr/local/bin v1.21.0
make config-reset make config-reset
make check-style BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}' make check-style BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}'
make build BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}' make build BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}'

Просмотреть файл

@@ -4,9 +4,12 @@
package commands package commands
import ( import (
"errors" "net/http"
"os" "os"
"path/filepath"
"github.com/mattermost/mattermost-server/model"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -55,14 +58,46 @@ var PluginListCmd = &cobra.Command{
RunE: pluginListCmdF, RunE: pluginListCmdF,
} }
var PluginPublicKeysCmd = &cobra.Command{
Use: "keys",
Short: "List public keys",
Long: "List names of all public keys installed on your Mattermost server.",
Example: ` plugin keys
plugin keys --verbose`,
RunE: pluginPublicKeysCmdF,
}
var PluginAddPublicKeyCmd = &cobra.Command{
Use: "add [keys]",
Short: "Adds public key(s)",
Long: "Adds public key(s) for plugins on your Mattermost server.",
Example: ` plugin keys add my-pk-file1 my-pk-file2`,
RunE: pluginAddPublicKeyCmdF,
}
var PluginDeletePublicKeyCmd = &cobra.Command{
Use: "delete [keys]",
Short: "Deletes public key(s)",
Long: "Deletes public key(s) for plugins on your Mattermost server.",
Example: ` plugin keys delete my-pk-file1 my-pk-file2`,
RunE: pluginDeletePublicKeyCmdF,
}
func init() { func init() {
PluginPublicKeysCmd.Flags().Bool("verbose", false, "List names and details of all public keys installed on your Mattermost server.")
PluginPublicKeysCmd.AddCommand(
PluginAddPublicKeyCmd,
PluginDeletePublicKeyCmd,
)
PluginCmd.AddCommand( PluginCmd.AddCommand(
PluginAddCmd, PluginAddCmd,
PluginDeleteCmd, PluginDeleteCmd,
PluginEnableCmd, PluginEnableCmd,
PluginDisableCmd, PluginDisableCmd,
PluginListCmd, PluginListCmd,
PluginPublicKeysCmd,
) )
RootCmd.AddCommand(PluginCmd) RootCmd.AddCommand(PluginCmd)
} }
@@ -169,7 +204,7 @@ func pluginListCmdF(command *cobra.Command, args []string) error {
pluginsResp, appErr := a.GetPlugins() pluginsResp, appErr := a.GetPlugins()
if appErr != nil { if appErr != nil {
return errors.New("Unable to list plugins. Error: " + appErr.Error()) return errors.Wrap(appErr, "Unable to list plugins.")
} }
CommandPrettyPrintln("Listing active plugins") CommandPrettyPrintln("Listing active plugins")
@@ -184,3 +219,88 @@ func pluginListCmdF(command *cobra.Command, args []string) error {
return nil return nil
} }
func pluginPublicKeysCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
verbose, err := command.Flags().GetBool("verbose")
if err != nil {
return errors.Wrap(err, "Failed reading verbose flag.")
}
pluginPublicKeysResp, appErr := a.GetPluginPublicKeyFiles()
if appErr != nil {
return errors.Wrap(appErr, "Unable to list public keys.")
}
if verbose {
for _, publicKey := range pluginPublicKeysResp {
key, err := a.GetPublicKey(publicKey)
if err != nil {
CommandPrintErrorln("Unable to get plugin public key: " + publicKey + ". Error: " + err.Error())
}
CommandPrettyPrintln("Plugin name: " + publicKey + ". \nPublic key: \n" + string(key) + "\n")
}
} else {
for _, publicKey := range pluginPublicKeysResp {
CommandPrettyPrintln(publicKey)
}
}
return nil
}
func pluginAddPublicKeyCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
for _, pkFile := range args {
filename := filepath.Base(pkFile)
fileReader, err := os.Open(pkFile)
if err != nil {
return model.NewAppError("AddPublicKey", "api.plugin.add_public_key.open.app_error", nil, err.Error(), http.StatusInternalServerError)
}
defer fileReader.Close()
if err := a.AddPublicKey(filename, fileReader); err != nil {
CommandPrintErrorln("Unable to add public key: " + pkFile + ". Error: " + err.Error())
} else {
CommandPrettyPrintln("Added public key: " + pkFile)
}
}
return nil
}
func pluginDeletePublicKeyCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
for _, pkFile := range args {
if err := a.DeletePublicKey(pkFile); err != nil {
CommandPrintErrorln("Unable to delete public key: " + pkFile + ". Error: " + err.Error())
} else {
CommandPrettyPrintln("Deleted public key: " + pkFile)
}
}
return nil
}

Просмотреть файл

@@ -1,3 +1,5 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands package commands
import ( import (
@@ -44,3 +46,75 @@ func TestPlugin(t *testing.T) {
th.CheckCommand(t, "plugin", "delete", "testplugin") th.CheckCommand(t, "plugin", "delete", "testplugin")
} }
func TestPluginPublicKeys(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
cfg := th.Config()
cfg.PluginSettings.SignaturePublicKeyFiles = []string{"public-key"}
th.SetConfig(cfg)
output := th.CheckCommand(t, "plugin", "keys")
assert.Contains(t, output, "public-key")
assert.NotContains(t, output, "Plugin name:")
}
func TestPluginPublicKeyDetails(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
cfg := th.Config()
cfg.PluginSettings.SignaturePublicKeyFiles = []string{"public-key"}
th.SetConfig(cfg)
output := th.CheckCommand(t, "plugin", "keys", "--verbose", "true")
assert.Contains(t, output, "Plugin name: public-key")
output = th.CheckCommand(t, "plugin", "keys", "--verbose")
assert.Contains(t, output, "Plugin name: public-key")
}
func TestAddPluginPublicKeys(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
cfg := th.Config()
cfg.PluginSettings.SignaturePublicKeyFiles = []string{"public-key"}
th.SetConfig(cfg)
err := th.RunCommand(t, "plugin", "keys", "add", "pk1")
assert.NotNil(t, err)
}
func TestDeletePluginPublicKeys(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
cfg := th.Config()
cfg.PluginSettings.SignaturePublicKeyFiles = []string{"pk1"}
th.SetConfig(cfg)
output := th.CheckCommand(t, "plugin", "keys", "delete", "pk1")
assert.Contains(t, output, "Deleted public key: pk1")
}
func TestPluginPublicKeysFlow(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
path, _ := fileutils.FindDir("tests")
name := "test-public-key.plugin.gpg"
output := th.CheckCommand(t, "plugin", "keys", "add", filepath.Join(path, name))
assert.Contains(t, output, "Added public key: "+filepath.Join(path, name))
output = th.CheckCommand(t, "plugin", "keys")
assert.Contains(t, output, name)
assert.NotContains(t, output, "Plugin name:")
output = th.CheckCommand(t, "plugin", "keys", "--verbose")
assert.Contains(t, output, "Plugin name: "+name)
output = th.CheckCommand(t, "plugin", "keys", "delete", name)
assert.Contains(t, output, "Deleted public key: "+name)
}

Просмотреть файл

@@ -20,6 +20,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
props["RestrictDirectMessage"] = *c.TeamSettings.RestrictDirectMessage props["RestrictDirectMessage"] = *c.TeamSettings.RestrictDirectMessage
props["EnableXToLeaveChannelsFromLHS"] = strconv.FormatBool(*c.TeamSettings.EnableXToLeaveChannelsFromLHS) props["EnableXToLeaveChannelsFromLHS"] = strconv.FormatBool(*c.TeamSettings.EnableXToLeaveChannelsFromLHS)
props["TeammateNameDisplay"] = *c.TeamSettings.TeammateNameDisplay props["TeammateNameDisplay"] = *c.TeamSettings.TeammateNameDisplay
props["LockTeammateNameDisplay"] = strconv.FormatBool(*c.TeamSettings.LockTeammateNameDisplay)
props["ExperimentalPrimaryTeam"] = *c.TeamSettings.ExperimentalPrimaryTeam props["ExperimentalPrimaryTeam"] = *c.TeamSettings.ExperimentalPrimaryTeam
props["ExperimentalViewArchivedChannels"] = strconv.FormatBool(*c.TeamSettings.ExperimentalViewArchivedChannels) props["ExperimentalViewArchivedChannels"] = strconv.FormatBool(*c.TeamSettings.ExperimentalViewArchivedChannels)

Просмотреть файл

@@ -24,6 +24,8 @@ func Migrate(from, to string) error {
files := []string{*sourceConfig.SamlSettings.IdpCertificateFile, *sourceConfig.SamlSettings.PublicCertificateFile, files := []string{*sourceConfig.SamlSettings.IdpCertificateFile, *sourceConfig.SamlSettings.PublicCertificateFile,
*sourceConfig.SamlSettings.PrivateKeyFile} *sourceConfig.SamlSettings.PrivateKeyFile}
files = append(files, sourceConfig.PluginSettings.SignaturePublicKeyFiles...)
for _, file := range files { for _, file := range files {
err = migrateFile(file, source, destination) err = migrateFile(file, source, destination)

Просмотреть файл

@@ -1340,6 +1340,10 @@
"id": "api.file.write_file_locally.writing.app_error", "id": "api.file.write_file_locally.writing.app_error",
"translation": "Encountered an error writing to local server storage" "translation": "Encountered an error writing to local server storage"
}, },
{
"id": "api.getGroups.invalid_or_missing_channel_or_team_id",
"translation": "Invalid/Missing channel ID or Team ID."
},
{ {
"id": "api.incoming_webhook.disabled.app_error", "id": "api.incoming_webhook.disabled.app_error",
"translation": "Incoming webhooks have been disabled by the system admin." "translation": "Incoming webhooks have been disabled by the system admin."
@@ -1508,6 +1512,10 @@
"id": "api.outgoing_webhook.disabled.app_error", "id": "api.outgoing_webhook.disabled.app_error",
"translation": "Outgoing webhooks have been disabled by the system admin." "translation": "Outgoing webhooks have been disabled by the system admin."
}, },
{
"id": "api.plugin.add_public_key.open.app_error",
"translation": "An error occurred while opening the public key file."
},
{ {
"id": "api.plugin.install.download_failed.app_error", "id": "api.plugin.install.download_failed.app_error",
"translation": "An error occurred while downloading the plugin." "translation": "An error occurred while downloading the plugin."
@@ -1536,6 +1544,10 @@
"id": "api.plugin.upload.no_file.app_error", "id": "api.plugin.upload.no_file.app_error",
"translation": "Missing file in multipart/form request" "translation": "Missing file in multipart/form request"
}, },
{
"id": "api.plugin.verify_plugin.app_error",
"translation": "Unable to verify plugin signature."
},
{ {
"id": "api.post.check_for_out_of_channel_groups_mentions.message.multiple", "id": "api.post.check_for_out_of_channel_groups_mentions.message.multiple",
"translation": "@{{.Usernames}} and @{{.LastUsername}} did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups." "translation": "@{{.Usernames}} and @{{.LastUsername}} did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups."
@@ -1702,6 +1714,14 @@
"id": "api.push_notification.disabled.app_error", "id": "api.push_notification.disabled.app_error",
"translation": "Push Notifications are disabled on this server." "translation": "Push Notifications are disabled on this server."
}, },
{
"id": "api.push_notification.id_loaded.default_message",
"translation": "You've received a new message."
},
{
"id": "api.push_notification.id_loaded.fetch.app_error",
"translation": "An error occurred fetching the ID-loaded push notification"
},
{ {
"id": "api.push_notifications_ack.forward.app_error", "id": "api.push_notifications_ack.forward.app_error",
"translation": "An error occurred sending the receipt delivery to the push notification service" "translation": "An error occurred sending the receipt delivery to the push notification service"
@@ -2626,6 +2646,10 @@
"id": "api.user.send_welcome_email_and_forget.failed.error", "id": "api.user.send_welcome_email_and_forget.failed.error",
"translation": "Failed to send welcome email successfully" "translation": "Failed to send welcome email successfully"
}, },
{
"id": "api.user.update_active.cannot_enable_guest_when_guest_feature_is_disabled.app_error",
"translation": "You cannot activate a guest account because Guest Access feature is not enabled."
},
{ {
"id": "api.user.update_active.not_enable.app_error", "id": "api.user.update_active.not_enable.app_error",
"translation": "You cannot deactivate yourself because this feature is not enabled. Please contact your System Administrator." "translation": "You cannot deactivate yourself because this feature is not enabled. Please contact your System Administrator."
@@ -3462,6 +3486,10 @@
"id": "app.plugin.deactivate.app_error", "id": "app.plugin.deactivate.app_error",
"translation": "Unable to deactivate plugin" "translation": "Unable to deactivate plugin"
}, },
{
"id": "app.plugin.delete_public_key.delete.app_error",
"translation": "An error occurred while deleting the public key."
},
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Plugins have been disabled. Please check your logs for details." "translation": "Plugins have been disabled. Please check your logs for details."
@@ -3486,6 +3514,10 @@
"id": "app.plugin.get_plugins.app_error", "id": "app.plugin.get_plugins.app_error",
"translation": "Unable to get active plugins" "translation": "Unable to get active plugins"
}, },
{
"id": "app.plugin.get_public_key.get_file.app_error",
"translation": "An error occurred while getting the public key from the store."
},
{ {
"id": "app.plugin.get_statuses.app_error", "id": "app.plugin.get_statuses.app_error",
"translation": "Unable to get plugin statuses" "translation": "Unable to get plugin statuses"
@@ -3506,6 +3538,10 @@
"id": "app.plugin.invalid_id.app_error", "id": "app.plugin.invalid_id.app_error",
"translation": "Plugin Id must be at least {{.Min}} characters, at most {{.Max}} characters and match {{.Regex}}." "translation": "Plugin Id must be at least {{.Min}} characters, at most {{.Max}} characters and match {{.Regex}}."
}, },
{
"id": "app.plugin.invalid_version.app_error",
"translation": "Plugin version could not be parsed"
},
{ {
"id": "app.plugin.manifest.app_error", "id": "app.plugin.manifest.app_error",
"translation": "Unable to find manifest for extracted plugin" "translation": "Unable to find manifest for extracted plugin"
@@ -3518,14 +3554,26 @@
"id": "app.plugin.marketplace_disabled.app_error", "id": "app.plugin.marketplace_disabled.app_error",
"translation": "Marketplace has been disabled. Please check your logs for details." "translation": "Marketplace has been disabled. Please check your logs for details."
}, },
{
"id": "app.plugin.marketplace_plugin_request.app_error",
"translation": "Failed to decode the marketplace plugin request."
},
{ {
"id": "app.plugin.marketplace_plugins.app_error", "id": "app.plugin.marketplace_plugins.app_error",
"translation": "Failed to get plugins from the marketplace server." "translation": "Failed to get plugins from the marketplace server."
}, },
{
"id": "app.plugin.marketplace_plugins.not_found.app_error",
"translation": "Plugin not found."
},
{ {
"id": "app.plugin.marshal.app_error", "id": "app.plugin.marshal.app_error",
"translation": "Failed to marshal marketplace plugins." "translation": "Failed to marshal marketplace plugins."
}, },
{
"id": "app.plugin.modify_saml.app_error",
"translation": "Can't modify saml files."
},
{ {
"id": "app.plugin.mvdir.app_error", "id": "app.plugin.mvdir.app_error",
"translation": "Unable to move plugin from temporary directory to final destination. Another plugin may be using the same directory name." "translation": "Unable to move plugin from temporary directory to final destination. Another plugin may be using the same directory name."
@@ -3546,10 +3594,18 @@
"id": "app.plugin.restart.app_error", "id": "app.plugin.restart.app_error",
"translation": "Unable to restart plugin on upgrade." "translation": "Unable to restart plugin on upgrade."
}, },
{
"id": "app.plugin.signature_decode.app_error",
"translation": "Unable to decode base64 signature."
},
{ {
"id": "app.plugin.store_bundle.app_error", "id": "app.plugin.store_bundle.app_error",
"translation": "Unable to store the plugin to the configured file store." "translation": "Unable to store the plugin to the configured file store."
}, },
{
"id": "app.plugin.store_signature.app_error",
"translation": "Unable to store the plugin signature to the configured file store."
},
{ {
"id": "app.plugin.sync.list_filestore.app_error", "id": "app.plugin.sync.list_filestore.app_error",
"translation": "Error reading files from the plugins folder in the file store." "translation": "Error reading files from the plugins folder in the file store."
@@ -3566,6 +3622,14 @@
"id": "app.plugin.webapp_bundle.app_error", "id": "app.plugin.webapp_bundle.app_error",
"translation": "Unable to generate plugin webapp bundle." "translation": "Unable to generate plugin webapp bundle."
}, },
{
"id": "app.plugin.write_file.read.app_error",
"translation": "An error occurred while reading the file."
},
{
"id": "app.plugin.write_file.saving.app_error",
"translation": "An error occurred while saving the file."
},
{ {
"id": "app.role.check_roles_exist.role_not_found", "id": "app.role.check_roles_exist.role_not_found",
"translation": "The provided role does not exist" "translation": "The provided role does not exist"
@@ -7162,6 +7226,14 @@
"id": "store.sql_user.update.username_taken.app_error", "id": "store.sql_user.update.username_taken.app_error",
"translation": "This username is already taken. Please choose another." "translation": "This username is already taken. Please choose another."
}, },
{
"id": "store.sql_user.update_active_for_multiple_users.getting_changed_users.app_error",
"translation": "Unable to get the list of deactivate guests ids"
},
{
"id": "store.sql_user.update_active_for_multiple_users.updating.app_error",
"translation": "Unable to deactivate guests"
},
{ {
"id": "store.sql_user.update_auth_data.app_error", "id": "store.sql_user.update_auth_data.app_error",
"translation": "Unable to update the auth data" "translation": "Unable to update the auth data"

Просмотреть файл

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings"
"unicode/utf8" "unicode/utf8"
) )
@@ -217,3 +218,15 @@ func (l *BotList) Etag() string {
func MakeBotNotFoundError(userId string) *AppError { func MakeBotNotFoundError(userId string) *AppError {
return NewAppError("SqlBotStore.Get", "store.sql_bot.get.missing.app_error", map[string]interface{}{"user_id": userId}, "", http.StatusNotFound) return NewAppError("SqlBotStore.Get", "store.sql_bot.get.missing.app_error", map[string]interface{}{"user_id": userId}, "", http.StatusNotFound)
} }
func IsBotDMChannel(channel *Channel, botUserID string) bool {
if channel.Type != CHANNEL_DIRECT {
return false
}
if !strings.HasPrefix(channel.Name, botUserID+"__") && !strings.HasSuffix(channel.Name, "__"+botUserID) {
return false
}
return true
}

Просмотреть файл

@@ -683,3 +683,45 @@ func TestBotListEtag(t *testing.T) {
}) })
} }
} }
func TestIsBotChannel(t *testing.T) {
for _, test := range []struct {
Name string
Channel *Channel
Expected bool
}{
{
Name: "not a direct channel",
Channel: &Channel{Type: CHANNEL_OPEN},
Expected: false,
},
{
Name: "a direct channel with another user",
Channel: &Channel{
Name: "user1__user2",
Type: CHANNEL_DIRECT,
},
Expected: false,
},
{
Name: "a direct channel with the name containing the bot's ID first",
Channel: &Channel{
Name: "botUserID__user2",
Type: CHANNEL_DIRECT,
},
Expected: true,
},
{
Name: "a direct channel with the name containing the bot's ID second",
Channel: &Channel{
Name: "user1__botUserID",
Type: CHANNEL_DIRECT,
},
Expected: true,
},
} {
t.Run(test.Name, func(t *testing.T) {
assert.Equal(t, test.Expected, IsBotDMChannel(test.Channel, "botUserID"))
})
}
}

Просмотреть файл

@@ -4472,6 +4472,21 @@ func (c *Client4) InstallPluginFromUrl(downloadUrl string, force bool) (*Manifes
return ManifestFromJson(r.Body), BuildResponse(r) return ManifestFromJson(r.Body), BuildResponse(r)
} }
// InstallMarketplacePlugin will install marketplace plugin.
// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
func (c *Client4) InstallMarketplacePlugin(request *InstallMarketplacePluginRequest) (*Manifest, *Response) {
json, err := request.ToJson()
if err != nil {
return nil, &Response{Error: NewAppError("InstallMarketplacePlugin", "model.client.plugin_request_to_json.app_error", nil, err.Error(), http.StatusBadRequest)}
}
r, appErr := c.DoApiPost(c.GetPluginsRoute()+"/marketplace", json)
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return ManifestFromJson(r.Body), BuildResponse(r)
}
// GetPlugins will return a list of plugin manifests for currently active plugins. // GetPlugins will return a list of plugin manifests for currently active plugins.
// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. // WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
func (c *Client4) GetPlugins() (*PluginsResponse, *Response) { func (c *Client4) GetPlugins() (*PluginsResponse, *Response) {

Просмотреть файл

@@ -24,6 +24,9 @@ const (
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user" CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES = "inv_roles" CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES = "inv_roles"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES = "inv_schemes" CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES = "inv_schemes"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_BY_ID = "inv_emojis_by_id"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_ID_BY_NAME = "inv_emojis_id_by_name"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBER_COUNTS = "inv_channel_member_counts"
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_ALL_USERS = "inv_all_user_sessions" CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_ALL_USERS = "inv_all_user_sessions"
CLUSTER_EVENT_INSTALL_PLUGIN = "install_plugin" CLUSTER_EVENT_INSTALL_PLUGIN = "install_plugin"
CLUSTER_EVENT_REMOVE_PLUGIN = "remove_plugin" CLUSTER_EVENT_REMOVE_PLUGIN = "remove_plugin"

Просмотреть файл

@@ -48,6 +48,7 @@ const (
GENERIC_NOTIFICATION = "generic" GENERIC_NOTIFICATION = "generic"
GENERIC_NOTIFICATION_SERVER = "https://push-test.mattermost.com" GENERIC_NOTIFICATION_SERVER = "https://push-test.mattermost.com"
FULL_NOTIFICATION = "full" FULL_NOTIFICATION = "full"
ID_LOADED_NOTIFICATION = "id_loaded"
DIRECT_MESSAGE_ANY = "any" DIRECT_MESSAGE_ANY = "any"
DIRECT_MESSAGE_TEAM = "team" DIRECT_MESSAGE_TEAM = "team"
@@ -1521,6 +1522,7 @@ type TeamSettings struct {
ExperimentalEnableAutomaticReplies *bool ExperimentalEnableAutomaticReplies *bool
ExperimentalHideTownSquareinLHS *bool ExperimentalHideTownSquareinLHS *bool
ExperimentalTownSquareIsReadOnly *bool ExperimentalTownSquareIsReadOnly *bool
LockTeammateNameDisplay *bool
ExperimentalPrimaryTeam *string ExperimentalPrimaryTeam *string
ExperimentalDefaultChannels []string ExperimentalDefaultChannels []string
} }
@@ -1667,6 +1669,10 @@ func (s *TeamSettings) SetDefaults() {
if s.ExperimentalViewArchivedChannels == nil { if s.ExperimentalViewArchivedChannels == nil {
s.ExperimentalViewArchivedChannels = NewBool(false) s.ExperimentalViewArchivedChannels = NewBool(false)
} }
if s.LockTeammateNameDisplay == nil {
s.LockTeammateNameDisplay = NewBool(false)
}
} }
type ClientRequirements struct { type ClientRequirements struct {
@@ -2239,7 +2245,9 @@ type PluginSettings struct {
Plugins map[string]map[string]interface{} Plugins map[string]map[string]interface{}
PluginStates map[string]*PluginState PluginStates map[string]*PluginState
EnableMarketplace *bool EnableMarketplace *bool
RequirePluginSignature *bool
MarketplaceUrl *string MarketplaceUrl *string
SignaturePublicKeyFiles []string
} }
func (s *PluginSettings) SetDefaults(ls LogSettings) { func (s *PluginSettings) SetDefaults(ls LogSettings) {
@@ -2287,6 +2295,14 @@ func (s *PluginSettings) SetDefaults(ls LogSettings) {
if s.MarketplaceUrl == nil || *s.MarketplaceUrl == "" || *s.MarketplaceUrl == PLUGIN_SETTINGS_OLD_MARKETPLACE_URL { if s.MarketplaceUrl == nil || *s.MarketplaceUrl == "" || *s.MarketplaceUrl == PLUGIN_SETTINGS_OLD_MARKETPLACE_URL {
s.MarketplaceUrl = NewString(PLUGIN_SETTINGS_DEFAULT_MARKETPLACE_URL) s.MarketplaceUrl = NewString(PLUGIN_SETTINGS_DEFAULT_MARKETPLACE_URL)
} }
if s.RequirePluginSignature == nil {
s.RequirePluginSignature = NewBool(false)
}
if s.SignaturePublicKeyFiles == nil {
s.SignaturePublicKeyFiles = []string{}
}
} }
type GlobalRelayMessageExportSettings struct { type GlobalRelayMessageExportSettings struct {

Просмотреть файл

@@ -170,7 +170,6 @@ func TestConfigIsValidFakeAlgorithm(t *testing.T) {
require.Equal(t, "model.config.is_valid.saml_canonical_algorithm.app_error", err.Message) require.Equal(t, "model.config.is_valid.saml_canonical_algorithm.app_error", err.Message)
*c1.SamlSettings.CanonicalAlgorithm = temp *c1.SamlSettings.CanonicalAlgorithm = temp
temp = *c1.SamlSettings.SignatureAlgorithm
*c1.SamlSettings.SignatureAlgorithm = "Fake Algorithm" *c1.SamlSettings.SignatureAlgorithm = "Fake Algorithm"
err = c1.SamlSettings.isValid() err = c1.SamlSettings.isValid()
if err == nil { if err == nil {

Просмотреть файл

@@ -44,6 +44,9 @@ type PostAction struct {
// The text on the button, or in the select placeholder. // The text on the button, or in the select placeholder.
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
// If the action is disabled.
Disabled bool `json:"disabled,omitempty"`
// DataSource indicates the data source for the select action. If left // DataSource indicates the data source for the select action. If left
// empty, the select is populated from Options. Other supported values // empty, the select is populated from Options. Other supported values
// are "users" and "channels". // are "users" and "channels".

Просмотреть файл

@@ -4,17 +4,24 @@
package model package model
import ( import (
"bytes"
"encoding/base64"
"encoding/json" "encoding/json"
"io" "io"
"net/url" "net/url"
"strconv" "strconv"
"github.com/pkg/errors"
) )
// BaseMarketplacePlugin is a Mattermost plugin received from the marketplace server. // BaseMarketplacePlugin is a Mattermost plugin received from the marketplace server.
type BaseMarketplacePlugin struct { type BaseMarketplacePlugin struct {
HomepageURL string `json:"homepage_url"` HomepageURL string `json:"homepage_url"`
DownloadURL string `json:"download_url"`
IconData string `json:"icon_data"` IconData string `json:"icon_data"`
DownloadURL string `json:"download_url"`
ReleaseNotesURL string `json:"release_notes_url"`
// Signature represents a signature of a plugin saved in base64 encoding.
Signature string `json:"signature"`
Manifest *Manifest `json:"manifest"` Manifest *Manifest `json:"manifest"`
} }
@@ -48,6 +55,15 @@ func MarketplacePluginsFromReader(reader io.Reader) ([]*MarketplacePlugin, error
return plugins, nil return plugins, nil
} }
// DecodeSignature Decodes signature and returns ReadSeeker.
func (plugin *BaseMarketplacePlugin) DecodeSignature() (io.ReadSeeker, error) {
signatureBytes, err := base64.StdEncoding.DecodeString(plugin.Signature)
if err != nil {
return nil, errors.Wrap(err, "Unable to decode base64 signature.")
}
return bytes.NewReader(signatureBytes), nil
}
// MarketplacePluginFilter describes the parameters to request a list of plugins. // MarketplacePluginFilter describes the parameters to request a list of plugins.
type MarketplacePluginFilter struct { type MarketplacePluginFilter struct {
Page int Page int
@@ -67,3 +83,28 @@ func (filter *MarketplacePluginFilter) ApplyToURL(u *url.URL) {
q.Add("server_version", filter.ServerVersion) q.Add("server_version", filter.ServerVersion)
u.RawQuery = q.Encode() u.RawQuery = q.Encode()
} }
// InstallMarketplacePluginRequest struct describes parameters of the requested plugin.
type InstallMarketplacePluginRequest struct {
Id string `json:"id"`
Version string `json:"version"`
}
// PluginRequestFromReader decodes a json-encoded plugin request from the given io.Reader.
func PluginRequestFromReader(reader io.Reader) (*InstallMarketplacePluginRequest, error) {
var r *InstallMarketplacePluginRequest
err := json.NewDecoder(reader).Decode(&r)
if err != nil {
return nil, err
}
return r, nil
}
// ToJson method will return json from plugin request.
func (r *InstallMarketplacePluginRequest) ToJson() (string, error) {
b, err := json.Marshal(r)
if err != nil {
return "", err
}
return string(b), nil
}

Просмотреть файл

@@ -15,6 +15,7 @@ const (
PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn" PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn"
PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn" PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn"
PUSH_TYPE_ID_LOADED = "id_loaded"
PUSH_TYPE_MESSAGE = "message" PUSH_TYPE_MESSAGE = "message"
PUSH_TYPE_CLEAR = "clear" PUSH_TYPE_CLEAR = "clear"
PUSH_TYPE_UPDATE_BADGE = "update_badge" PUSH_TYPE_UPDATE_BADGE = "update_badge"
@@ -39,6 +40,7 @@ type PushNotificationAck struct {
ClientReceivedAt int64 `json:"received_at"` ClientReceivedAt int64 `json:"received_at"`
ClientPlatform string `json:"platform"` ClientPlatform string `json:"platform"`
NotificationType string `json:"type"` NotificationType string `json:"type"`
PostId string `json:"post_id,omitempty"`
} }
type PushNotification struct { type PushNotification struct {
@@ -46,23 +48,23 @@ type PushNotification struct {
Platform string `json:"platform"` Platform string `json:"platform"`
ServerId string `json:"server_id"` ServerId string `json:"server_id"`
DeviceId string `json:"device_id"` DeviceId string `json:"device_id"`
Category string `json:"category"`
Sound string `json:"sound"`
Message string `json:"message"`
Badge int `json:"badge"`
ContentAvailable int `json:"cont_ava"`
TeamId string `json:"team_id"`
ChannelId string `json:"channel_id"`
PostId string `json:"post_id"` PostId string `json:"post_id"`
RootId string `json:"root_id"` Category string `json:"category,omitempty"`
ChannelName string `json:"channel_name"` Sound string `json:"sound,omitempty"`
Type string `json:"type"` Message string `json:"message,omitempty"`
SenderId string `json:"sender_id"` Badge int `json:"badge,omitempty"`
SenderName string `json:"sender_name"` ContentAvailable int `json:"cont_ava,omitempty"`
OverrideUsername string `json:"override_username"` TeamId string `json:"team_id,omitempty"`
OverrideIconUrl string `json:"override_icon_url"` ChannelId string `json:"channel_id,omitempty"`
FromWebhook string `json:"from_webhook"` RootId string `json:"root_id,omitempty"`
Version string `json:"version"` ChannelName string `json:"channel_name,omitempty"`
Type string `json:"type,omitempty"`
SenderId string `json:"sender_id,omitempty"`
SenderName string `json:"sender_name,omitempty"`
OverrideUsername string `json:"override_username,omitempty"`
OverrideIconUrl string `json:"override_icon_url,omitempty"`
FromWebhook string `json:"from_webhook,omitempty"`
Version string `json:"version,omitempty"`
} }
func (me *PushNotification) ToJson() string { func (me *PushNotification) ToJson() string {

Просмотреть файл

@@ -268,6 +268,7 @@ func CleanTeamName(s string) string {
func (o *Team) Sanitize() { func (o *Team) Sanitize() {
o.Email = "" o.Email = ""
o.InviteId = ""
} }
func (t *Team) Patch(patch *TeamPatch) { func (t *Team) Patch(patch *TeamPatch) {

Просмотреть файл

@@ -624,3 +624,8 @@ func GetPreferredTimezone(timezone StringMap) string {
return timezone["manualTimezone"] return timezone["manualTimezone"]
} }
// IsSamlFile checks if filename is a SAML file.
func IsSamlFile(saml *SamlSettings, filename string) bool {
return filename == *saml.PublicCertificateFile || filename == *saml.PrivateKeyFile || filename == *saml.IdpCertificateFile
}

Просмотреть файл

@@ -52,6 +52,7 @@ const (
WEBSOCKET_EVENT_LICENSE_CHANGED = "license_changed" WEBSOCKET_EVENT_LICENSE_CHANGED = "license_changed"
WEBSOCKET_EVENT_CONFIG_CHANGED = "config_changed" WEBSOCKET_EVENT_CONFIG_CHANGED = "config_changed"
WEBSOCKET_EVENT_OPEN_DIALOG = "open_dialog" WEBSOCKET_EVENT_OPEN_DIALOG = "open_dialog"
WEBSOCKET_EVENT_GUESTS_DEACTIVATED = "guests_deactivated"
) )
type WebSocketMessage interface { type WebSocketMessage interface {

Просмотреть файл

@@ -664,7 +664,7 @@ type API interface {
// Minimum server version: 5.6 // Minimum server version: 5.6
GetFileLink(fileId string) (string, *model.AppError) GetFileLink(fileId string) (string, *model.AppError)
// ReadFileAtPath reads the file from the backend for a specific path // ReadFile reads the file from the backend for a specific path
// //
// @tag File // @tag File
// Minimum server version: 5.3 // Minimum server version: 5.3

Просмотреть файл

@@ -525,7 +525,7 @@ func (s *hooksRPCServer) FileWillBeUploaded(args *Z_FileWillBeUploadedArgs, retu
return nil return nil
} }
// MessageWillBePosted is in this file because of the difficulty of identifiying which fields need special behaviour. // MessageWillBePosted is in this file because of the difficulty of identifying which fields need special behaviour.
// The special behaviour needed is decoding the returned post into the original one to avoid the unintentional removal // The special behaviour needed is decoding the returned post into the original one to avoid the unintentional removal
// of fields by older plugins. // of fields by older plugins.
func init() { func init() {
@@ -565,8 +565,8 @@ func (s *hooksRPCServer) MessageWillBePosted(args *Z_MessageWillBePostedArgs, re
return nil return nil
} }
// MessageWillBeUpdated is in this file because of the difficulty of identifiying which fields need special behaviour. // MessageWillBeUpdated is in this file because of the difficulty of identifying which fields need special behaviour.
// The special behavour needed is decoding the returned post into the original one to avoid the unintentional removal // The special behaviour needed is decoding the returned post into the original one to avoid the unintentional removal
// of fields by older plugins. // of fields by older plugins.
func init() { func init() {
hookNameToId["MessageWillBeUpdated"] = MessageWillBeUpdatedId hookNameToId["MessageWillBeUpdated"] = MessageWillBeUpdatedId

Просмотреть файл

@@ -14,10 +14,11 @@ import (
// Plugins obtain access to the Helpers by embedding MattermostPlugin. // Plugins obtain access to the Helpers by embedding MattermostPlugin.
type Helpers interface { type Helpers interface {
// EnsureBot either returns an existing bot user matching the given bot, or creates a bot user from the given bot. // EnsureBot either returns an existing bot user matching the given bot, or creates a bot user from the given bot.
// A profile image or icon image may be optionally passed in to be set for the existing or newly created bot.
// Returns the id of the resulting bot. // Returns the id of the resulting bot.
// //
// Minimum server version: 5.10 // Minimum server version: 5.10
EnsureBot(bot *model.Bot) (string, error) EnsureBot(bot *model.Bot, options ...EnsureBotOption) (string, error)
// KVSetJSON stores a key-value pair, unique per plugin, marshalling the given value as a JSON string. // KVSetJSON stores a key-value pair, unique per plugin, marshalling the given value as a JSON string.
// //
@@ -56,6 +57,18 @@ type Helpers interface {
// //
// Minimum server version: 5.6 // Minimum server version: 5.6
KVSetWithExpiryJSON(key string, value interface{}, expireInSeconds int64) error KVSetWithExpiryJSON(key string, value interface{}, expireInSeconds int64) error
// ShouldProcessMessage returns if the message should be processed by a message hook.
//
// Use this method to avoid processing unnecessary messages in a MessageHasBeenPosted
// or MessageWillBePosted hook, and indeed in some cases avoid an infinite loop between
// two automated bots or plugins.
//
// The behaviour is customizable using the given options, since plugin needs may vary.
// By default, system messages and messages from bots will be skipped.
//
// Minimum server version: 5.2
ShouldProcessMessage(post *model.Post, options ...ShouldProcessMessageOption) (bool, error)
} }
// HelpersImpl implements the helpers interface with an API that retrieves data on behalf of the plugin. // HelpersImpl implements the helpers interface with an API that retrieves data on behalf of the plugin.

Просмотреть файл

@@ -4,19 +4,187 @@
package plugin package plugin
import ( import (
"io/ioutil"
"path/filepath"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
) )
type ensureBotOptions struct {
ProfileImagePath string
IconImagePath string
}
type EnsureBotOption func(*ensureBotOptions)
func ProfileImagePath(path string) EnsureBotOption {
return func(args *ensureBotOptions) {
args.ProfileImagePath = path
}
}
func IconImagePath(path string) EnsureBotOption {
return func(args *ensureBotOptions) {
args.IconImagePath = path
}
}
// EnsureBot implements Helpers.EnsureBot // EnsureBot implements Helpers.EnsureBot
func (p *HelpersImpl) EnsureBot(bot *model.Bot) (retBotID string, retErr error) { func (p *HelpersImpl) EnsureBot(bot *model.Bot, options ...EnsureBotOption) (retBotID string, retErr error) {
err := p.ensureServerVersion("5.10.0") err := p.ensureServerVersion("5.10.0")
if err != nil { if err != nil {
return "", errors.Wrap(err, "failed to ensure bot") return "", errors.Wrap(err, "failed to ensure bot")
} }
// Default options
o := &ensureBotOptions{
ProfileImagePath: "",
IconImagePath: "",
}
for _, setter := range options {
setter(o)
}
botID, err := p.ensureBot(bot)
if err != nil {
return "", err
}
err = p.setBotImages(botID, o.ProfileImagePath, o.IconImagePath)
if err != nil {
return "", err
}
return botID, nil
}
type ShouldProcessMessageOption func(*shouldProcessMessageOptions)
type shouldProcessMessageOptions struct {
AllowSystemMessages bool
AllowBots bool
FilterChannelIDs []string
FilterUserIDs []string
OnlyBotDMs bool
}
// AllowSystemMessages configures a call to ShouldProcessMessage to return true for system messages.
//
// As it is typically desirable only to consume messages from users of the system, ShouldProcessMessage ignores system messages by default.
func AllowSystemMessages() ShouldProcessMessageOption {
return func(options *shouldProcessMessageOptions) {
options.AllowSystemMessages = true
}
}
// AllowBots configures a call to ShouldProcessMessage to return true for bot posts.
//
// As it is typically desirable only to consume messages from human users of the system, ShouldProcessMessage ignores bot messages by default. When allowing bots, take care to avoid a loop where two plugins respond to each others posts repeatedly.
func AllowBots() ShouldProcessMessageOption {
return func(options *shouldProcessMessageOptions) {
options.AllowBots = true
}
}
// FilterChannelIDs configures a call to ShouldProcessMessage to return true only for the given channels.
//
// By default, posts from all channels are allowed to be processed.
func FilterChannelIDs(filterChannelIDs []string) ShouldProcessMessageOption {
return func(options *shouldProcessMessageOptions) {
options.FilterChannelIDs = filterChannelIDs
}
}
// FilterUserIDs configures a call to ShouldProcessMessage to return true only for the given users.
//
// By default, posts from all non-bot users are allowed.
func FilterUserIDs(filterUserIDs []string) ShouldProcessMessageOption {
return func(options *shouldProcessMessageOptions) {
options.FilterUserIDs = filterUserIDs
}
}
// OnlyBotDMs configures a call to ShouldProcessMessage to return true only for direct messages sent to the bot created by EnsureBot.
//
// By default, posts from all channels are allowed.
func OnlyBotDMs() ShouldProcessMessageOption {
return func(options *shouldProcessMessageOptions) {
options.OnlyBotDMs = true
}
}
// ShouldProcessMessage implements Helpers.ShouldProcessMessage
func (p *HelpersImpl) ShouldProcessMessage(post *model.Post, options ...ShouldProcessMessageOption) (bool, error) {
messageProcessOptions := &shouldProcessMessageOptions{}
for _, option := range options {
option(messageProcessOptions)
}
botIdBytes, kvGetErr := p.API.KVGet(BOT_USER_KEY)
if kvGetErr != nil {
return false, errors.Wrap(kvGetErr, "failed to get bot")
}
if botIdBytes != nil {
if post.UserId == string(botIdBytes) {
return false, nil
}
}
if post.IsSystemMessage() && !messageProcessOptions.AllowSystemMessages {
return false, nil
}
if !messageProcessOptions.AllowBots {
user, appErr := p.API.GetUser(post.UserId)
if appErr != nil {
return false, errors.Wrap(appErr, "unable to get user")
}
if user.IsBot {
return false, nil
}
}
if len(messageProcessOptions.FilterChannelIDs) != 0 && !utils.StringInSlice(post.ChannelId, messageProcessOptions.FilterChannelIDs) {
return false, nil
}
if len(messageProcessOptions.FilterUserIDs) != 0 && !utils.StringInSlice(post.UserId, messageProcessOptions.FilterUserIDs) {
return false, nil
}
if botIdBytes != nil && messageProcessOptions.OnlyBotDMs {
channel, appErr := p.API.GetChannel(post.ChannelId)
if appErr != nil {
return false, errors.Wrap(appErr, "unable to get channel")
}
if !model.IsBotDMChannel(channel, string(botIdBytes)) {
return false, nil
}
}
return true, nil
}
func (p *HelpersImpl) readFile(path string) ([]byte, error) {
bundlePath, err := p.API.GetBundlePath()
if err != nil {
return nil, errors.Wrap(err, "failed to get bundle path")
}
imageBytes, err := ioutil.ReadFile(filepath.Join(bundlePath, path))
if err != nil {
return nil, errors.Wrap(err, "failed to read image")
}
return imageBytes, nil
}
func (p *HelpersImpl) ensureBot(bot *model.Bot) (retBotID string, retErr error) {
// Must provide a bot with a username // Must provide a bot with a username
if bot == nil || len(bot.Username) < 1 { if bot == nil || len(bot.Username) < 1 {
return "", errors.New("passed a bad bot, nil or no username") return "", errors.New("passed a bad bot, nil or no username")
@@ -79,3 +247,27 @@ func (p *HelpersImpl) EnsureBot(bot *model.Bot) (retBotID string, retErr error)
return createdBot.UserId, nil return createdBot.UserId, nil
} }
func (p *HelpersImpl) setBotImages(botID, profileImagePath, iconImagePath string) error {
if profileImagePath != "" {
imageBytes, err := p.readFile(profileImagePath)
if err != nil {
return errors.Wrap(err, "failed to read profile image")
}
appErr := p.API.SetProfileImage(botID, imageBytes)
if appErr != nil {
return errors.Wrap(appErr, "failed to set profile image")
}
}
if iconImagePath != "" {
imageBytes, err := p.readFile(iconImagePath)
if err != nil {
return errors.Wrap(err, "failed to read icon image")
}
appErr := p.API.SetBotIconImage(botID, imageBytes)
if appErr != nil {
return errors.Wrap(appErr, "failed to set icon image")
}
}
return nil
}

Просмотреть файл

@@ -4,12 +4,15 @@
package plugin_test package plugin_test
import ( import (
"io/ioutil"
"path/filepath"
"testing" "testing"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/plugin" "github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/plugin/plugintest" "github.com/mattermost/mattermost-server/plugin/plugintest"
"github.com/mattermost/mattermost-server/plugin/plugintest/mock" "github.com/mattermost/mattermost-server/plugin/plugintest/mock"
"github.com/mattermost/mattermost-server/utils/fileutils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -95,6 +98,77 @@ func TestEnsureBot(t *testing.T) {
assert.Equal(t, "", botId) assert.Equal(t, "", botId)
assert.NotNil(t, err) assert.NotNil(t, err)
}) })
t.Run("should set the bot profile image when specified", func(t *testing.T) {
expectedBotId := model.NewId()
api := setupAPI()
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
imageBytes, err := ioutil.ReadFile(testImage)
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
api.On("GetBundlePath").Return("", nil)
api.On("SetProfileImage", expectedBotId, imageBytes).Return(nil)
api.On("GetServerVersion").Return("5.10.0")
defer api.AssertExpectations(t)
p := &plugin.HelpersImpl{}
p.API = api
assert.Nil(t, err)
botId, err := p.EnsureBot(testbot, plugin.ProfileImagePath(testImage))
assert.Equal(t, expectedBotId, botId)
assert.Nil(t, err)
})
t.Run("should set the bot icon image when specified", func(t *testing.T) {
expectedBotId := model.NewId()
api := setupAPI()
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
imageBytes, err := ioutil.ReadFile(testImage)
assert.Nil(t, err)
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
api.On("GetBundlePath").Return("", nil)
api.On("SetBotIconImage", expectedBotId, imageBytes).Return(nil)
api.On("GetServerVersion").Return("5.10.0")
defer api.AssertExpectations(t)
p := &plugin.HelpersImpl{}
p.API = api
botId, err := p.EnsureBot(testbot, plugin.IconImagePath(testImage))
assert.Equal(t, expectedBotId, botId)
assert.Nil(t, err)
})
t.Run("should set both the profile image and bot icon image when specified", func(t *testing.T) {
expectedBotId := model.NewId()
api := setupAPI()
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
imageBytes, err := ioutil.ReadFile(testImage)
assert.Nil(t, err)
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
api.On("GetBundlePath").Return("", nil)
api.On("SetProfileImage", expectedBotId, imageBytes).Return(nil)
api.On("SetBotIconImage", expectedBotId, imageBytes).Return(nil)
api.On("GetServerVersion").Return("5.10.0")
defer api.AssertExpectations(t)
p := &plugin.HelpersImpl{}
p.API = api
botId, err := p.EnsureBot(testbot, plugin.ProfileImagePath(testImage), plugin.IconImagePath(testImage))
assert.Equal(t, expectedBotId, botId)
assert.Nil(t, err)
})
}) })
t.Run("if bot doesn't exist", func(t *testing.T) { t.Run("if bot doesn't exist", func(t *testing.T) {
@@ -179,5 +253,198 @@ func TestEnsureBot(t *testing.T) {
assert.Equal(t, "", botId) assert.Equal(t, "", botId)
assert.NotNil(t, err) assert.NotNil(t, err)
}) })
t.Run("should create bot and set the bot profile image when specified", func(t *testing.T) {
expectedBotId := model.NewId()
api := setupAPI()
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
imageBytes, err := ioutil.ReadFile(testImage)
assert.Nil(t, err)
api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, nil)
api.On("GetUserByUsername", testbot.Username).Return(nil, nil)
api.On("CreateBot", testbot).Return(&model.Bot{
UserId: expectedBotId,
}, nil)
api.On("KVSet", plugin.BOT_USER_KEY, []byte(expectedBotId)).Return(nil)
api.On("GetBundlePath").Return("", nil)
api.On("SetProfileImage", expectedBotId, imageBytes).Return(nil)
api.On("GetServerVersion").Return("5.10.0")
defer api.AssertExpectations(t)
p := &plugin.HelpersImpl{}
p.API = api
botId, err := p.EnsureBot(testbot, plugin.ProfileImagePath(testImage))
assert.Equal(t, expectedBotId, botId)
assert.Nil(t, err)
})
t.Run("should create bot and set the bot icon image when specified", func(t *testing.T) {
expectedBotId := model.NewId()
api := setupAPI()
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
imageBytes, err := ioutil.ReadFile(testImage)
assert.Nil(t, err)
api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, nil)
api.On("GetUserByUsername", testbot.Username).Return(nil, nil)
api.On("CreateBot", testbot).Return(&model.Bot{
UserId: expectedBotId,
}, nil)
api.On("KVSet", plugin.BOT_USER_KEY, []byte(expectedBotId)).Return(nil)
api.On("GetBundlePath").Return("", nil)
api.On("SetBotIconImage", expectedBotId, imageBytes).Return(nil)
api.On("GetServerVersion").Return("5.10.0")
defer api.AssertExpectations(t)
p := &plugin.HelpersImpl{}
p.API = api
botId, err := p.EnsureBot(testbot, plugin.IconImagePath(testImage))
assert.Equal(t, expectedBotId, botId)
assert.Nil(t, err)
})
t.Run("should create bot and set both the profile image and bot icon image when specified", func(t *testing.T) {
expectedBotId := model.NewId()
api := setupAPI()
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
imageBytes, err := ioutil.ReadFile(testImage)
assert.Nil(t, err)
api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, nil)
api.On("GetUserByUsername", testbot.Username).Return(nil, nil)
api.On("CreateBot", testbot).Return(&model.Bot{
UserId: expectedBotId,
}, nil)
api.On("KVSet", plugin.BOT_USER_KEY, []byte(expectedBotId)).Return(nil)
api.On("GetBundlePath").Return("", nil)
api.On("SetProfileImage", expectedBotId, imageBytes).Return(nil)
api.On("SetBotIconImage", expectedBotId, imageBytes).Return(nil)
api.On("GetServerVersion").Return("5.10.0")
defer api.AssertExpectations(t)
p := &plugin.HelpersImpl{}
p.API = api
botId, err := p.EnsureBot(testbot, plugin.ProfileImagePath(testImage), plugin.IconImagePath(testImage))
assert.Equal(t, expectedBotId, botId)
assert.Nil(t, err)
})
})
}
func TestShouldProcessMessage(t *testing.T) {
p := &plugin.HelpersImpl{}
expectedBotId := model.NewId()
setupAPI := func() *plugintest.API {
return &plugintest.API{}
}
t.Run("should not respond to itself", func(t *testing.T) {
api := setupAPI()
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
p.API = api
shouldProcessMessage, _ := p.ShouldProcessMessage(&model.Post{Type: model.POST_HEADER_CHANGE, UserId: expectedBotId}, plugin.AllowSystemMessages(), plugin.AllowBots())
assert.False(t, shouldProcessMessage)
})
t.Run("should not process as the post is generated by system", func(t *testing.T) {
shouldProcessMessage, _ := p.ShouldProcessMessage(&model.Post{Type: model.POST_HEADER_CHANGE})
assert.False(t, shouldProcessMessage)
})
t.Run("should not process as the post is sent to another channel", func(t *testing.T) {
channelID := "channel-id"
api := setupAPI()
api.On("GetChannel", channelID).Return(&model.Channel{Id: channelID, Type: model.CHANNEL_GROUP}, nil)
p.API = api
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
shouldProcessMessage, _ := p.ShouldProcessMessage(&model.Post{ChannelId: channelID}, plugin.AllowSystemMessages(), plugin.AllowBots(), plugin.FilterChannelIDs([]string{"another-channel-id"}))
assert.False(t, shouldProcessMessage)
})
t.Run("should not process as the post is created by bot", func(t *testing.T) {
userID := "user-id"
channelID := "1"
api := setupAPI()
p.API = api
api.On("GetUser", userID).Return(&model.User{IsBot: true}, nil)
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
shouldProcessMessage, _ := p.ShouldProcessMessage(&model.Post{UserId: userID, ChannelId: channelID},
plugin.AllowSystemMessages(), plugin.FilterUserIDs([]string{"another-user-id"}))
assert.False(t, shouldProcessMessage)
})
t.Run("should not process the message as the post is not in bot dm channel", func(t *testing.T) {
userID := "user-id"
channelID := "1"
channel := model.Channel{
Name: "user1__" + expectedBotId,
Type: model.CHANNEL_OPEN,
}
api := setupAPI()
api.On("GetChannel", channelID).Return(&channel, nil)
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
p.API = api
shouldProcessMessage, _ := p.ShouldProcessMessage(&model.Post{UserId: userID, ChannelId: channelID}, plugin.AllowSystemMessages(), plugin.AllowBots(), plugin.OnlyBotDMs())
assert.False(t, shouldProcessMessage)
})
t.Run("should process the message", func(t *testing.T) {
channelID := "1"
api := setupAPI()
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
p.API = api
shouldProcessMessage, _ := p.ShouldProcessMessage(&model.Post{UserId: "1", Type: model.POST_HEADER_CHANGE, ChannelId: channelID},
plugin.AllowSystemMessages(), plugin.FilterChannelIDs([]string{channelID}), plugin.AllowBots(), plugin.FilterUserIDs([]string{"1"}))
assert.True(t, shouldProcessMessage)
})
t.Run("should process the message for plugin without a bot", func(t *testing.T) {
channelID := "1"
api := setupAPI()
api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, nil)
p.API = api
shouldProcessMessage, _ := p.ShouldProcessMessage(&model.Post{UserId: "1", Type: model.POST_HEADER_CHANGE, ChannelId: channelID},
plugin.AllowSystemMessages(), plugin.FilterChannelIDs([]string{channelID}), plugin.AllowBots(), plugin.FilterUserIDs([]string{"1"}))
assert.True(t, shouldProcessMessage)
})
t.Run("should process the message when filter channel and filter users list is empty", func(t *testing.T) {
channelID := "1"
api := setupAPI()
channel := model.Channel{
Name: "user1__" + expectedBotId,
Type: model.CHANNEL_DIRECT,
}
api.On("GetChannel", channelID).Return(&channel, nil)
api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil)
p.API = api
shouldProcessMessage, _ := p.ShouldProcessMessage(&model.Post{UserId: "1", Type: model.POST_HEADER_CHANGE, ChannelId: channelID},
plugin.AllowSystemMessages(), plugin.AllowBots())
assert.True(t, shouldProcessMessage)
}) })
} }

Просмотреть файл

@@ -6,6 +6,7 @@ package plugintest
import ( import (
model "github.com/mattermost/mattermost-server/model" model "github.com/mattermost/mattermost-server/model"
plugin "github.com/mattermost/mattermost-server/plugin"
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
) )
@@ -14,20 +15,27 @@ type Helpers struct {
mock.Mock mock.Mock
} }
// EnsureBot provides a mock function with given fields: bot // EnsureBot provides a mock function with given fields: bot, options
func (_m *Helpers) EnsureBot(bot *model.Bot) (string, error) { func (_m *Helpers) EnsureBot(bot *model.Bot, options ...plugin.EnsureBotOption) (string, error) {
ret := _m.Called(bot) _va := make([]interface{}, len(options))
for _i := range options {
_va[_i] = options[_i]
}
var _ca []interface{}
_ca = append(_ca, bot)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 string var r0 string
if rf, ok := ret.Get(0).(func(*model.Bot) string); ok { if rf, ok := ret.Get(0).(func(*model.Bot, ...plugin.EnsureBotOption) string); ok {
r0 = rf(bot) r0 = rf(bot, options...)
} else { } else {
r0 = ret.Get(0).(string) r0 = ret.Get(0).(string)
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func(*model.Bot) error); ok { if rf, ok := ret.Get(1).(func(*model.Bot, ...plugin.EnsureBotOption) error); ok {
r1 = rf(bot) r1 = rf(bot, options...)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }
@@ -125,3 +133,31 @@ func (_m *Helpers) KVSetWithExpiryJSON(key string, value interface{}, expireInSe
return r0 return r0
} }
// ShouldProcessMessage provides a mock function with given fields: post, options
func (_m *Helpers) ShouldProcessMessage(post *model.Post, options ...plugin.ShouldProcessMessageOption) (bool, error) {
_va := make([]interface{}, len(options))
for _i := range options {
_va[_i] = options[_i]
}
var _ca []interface{}
_ca = append(_ca, post)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 bool
if rf, ok := ret.Get(0).(func(*model.Post, ...plugin.ShouldProcessMessageOption) bool); ok {
r0 = rf(post, options...)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.Post, ...plugin.ShouldProcessMessageOption) error); ok {
r1 = rf(post, options...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

Просмотреть файл

@@ -62,6 +62,19 @@ func (c *Client) GetPlugins(request *model.MarketplacePluginFilter) ([]*model.Ba
} }
} }
func (c *Client) GetPlugin(filter *model.MarketplacePluginFilter, pluginVersion string) (*model.BaseMarketplacePlugin, error) {
plugins, err := c.GetPlugins(filter)
if err != nil {
return nil, err
}
for _, plugin := range plugins {
if plugin.Manifest.Version == pluginVersion {
return plugin, nil
}
}
return nil, errors.New("plugin not found")
}
// closeBody ensures the Body of an http.Response is properly closed. // closeBody ensures the Body of an http.Response is properly closed.
func closeBody(r *http.Response) { func closeBody(r *http.Response) {
if r.Body != nil { if r.Body != nil {

65
store/localcachelayer/channel_layer.go Обычный файл
Просмотреть файл

@@ -0,0 +1,65 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package localcachelayer
import (
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
type LocalCacheChannelStore struct {
store.ChannelStore
rootStore *LocalCacheStore
}
func (s *LocalCacheChannelStore) handleClusterInvalidateChannelMemberCounts(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
s.rootStore.channelMemberCountsCache.Purge()
} else {
s.rootStore.channelMemberCountsCache.Remove(msg.Data)
}
}
func (s LocalCacheChannelStore) ClearCaches() {
s.rootStore.doClearCacheCluster(s.rootStore.channelMemberCountsCache)
s.ChannelStore.ClearCaches()
if s.rootStore.metrics != nil {
s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel Member Counts - Purge")
}
}
func (s LocalCacheChannelStore) InvalidateMemberCount(channelId string) {
s.rootStore.doInvalidateCacheCluster(s.rootStore.channelMemberCountsCache, channelId)
if s.rootStore.metrics != nil {
s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel Member Counts - Remove by ChannelId")
}
}
func (s LocalCacheChannelStore) GetMemberCount(channelId string, allowFromCache bool) (int64, *model.AppError) {
if allowFromCache {
if count := s.rootStore.doStandardReadCache(s.rootStore.channelMemberCountsCache, channelId); count != nil {
return count.(int64), nil
}
}
count, err := s.ChannelStore.GetMemberCount(channelId, allowFromCache)
if allowFromCache && err == nil {
s.rootStore.doStandardAddToCache(s.rootStore.channelMemberCountsCache, channelId, count)
}
return count, err
}
func (s LocalCacheChannelStore) GetMemberCountFromCache(channelId string) int64 {
if count := s.rootStore.doStandardReadCache(s.rootStore.channelMemberCountsCache, channelId); count != nil {
return count.(int64)
}
count, err := s.GetMemberCount(channelId, true)
if err != nil {
return 0
}
return count
}

88
store/localcachelayer/channel_layer_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,88 @@
package localcachelayer
import (
"testing"
"github.com/mattermost/mattermost-server/store/storetest"
"github.com/mattermost/mattermost-server/store/storetest/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestChannelStore(t *testing.T) {
StoreTest(t, storetest.TestReactionStore)
}
func TestChannelStoreChannelMemberCountsCache(t *testing.T) {
countResult := int64(10)
t.Run("first call not cached, second cached and returning same data", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
count, err := cachedStore.Channel().GetMemberCount("id", true)
require.Nil(t, err)
assert.Equal(t, count, countResult)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 1)
count, err = cachedStore.Channel().GetMemberCount("id", true)
require.Nil(t, err)
assert.Equal(t, count, countResult)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 1)
})
t.Run("first call not cached, second force no cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Channel().GetMemberCount("id", true)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 1)
cachedStore.Channel().GetMemberCount("id", false)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 2)
})
t.Run("first call force no cached, second not cached, third cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Channel().GetMemberCount("id", false)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 1)
cachedStore.Channel().GetMemberCount("id", true)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 2)
cachedStore.Channel().GetMemberCount("id", true)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 2)
})
t.Run("first call with GetMemberCountFromCache not cached, second cached and returning same data", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
count := cachedStore.Channel().GetMemberCountFromCache("id")
assert.Equal(t, count, countResult)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 1)
count = cachedStore.Channel().GetMemberCountFromCache("id")
assert.Equal(t, count, countResult)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 1)
})
t.Run("first call not cached, clear cache, second call not cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Channel().GetMemberCount("id", true)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 1)
cachedStore.Channel().ClearCaches()
cachedStore.Channel().GetMemberCount("id", true)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 2)
})
t.Run("first call not cached, invalidate cache, second call not cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Channel().GetMemberCount("id", true)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 1)
cachedStore.Channel().InvalidateMemberCount("id")
cachedStore.Channel().GetMemberCount("id", true)
mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMemberCount", 2)
})
}

100
store/localcachelayer/emoji_layer.go Обычный файл
Просмотреть файл

@@ -0,0 +1,100 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package localcachelayer
import (
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
type LocalCacheEmojiStore struct {
store.EmojiStore
rootStore *LocalCacheStore
}
func (es *LocalCacheEmojiStore) handleClusterInvalidateEmojiById(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
es.rootStore.emojiCacheById.Purge()
} else {
es.rootStore.emojiCacheById.Remove(msg.Data)
}
}
func (es *LocalCacheEmojiStore) handleClusterInvalidateEmojiIdByName(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
es.rootStore.emojiIdCacheByName.Purge()
} else {
es.rootStore.emojiIdCacheByName.Remove(msg.Data)
}
}
func (es LocalCacheEmojiStore) Get(id string, allowFromCache bool) (*model.Emoji, *model.AppError) {
if allowFromCache {
if emoji, ok := es.getFromCacheById(id); ok {
return emoji, nil
}
}
emoji, err := es.EmojiStore.Get(id, allowFromCache)
if allowFromCache && err == nil {
es.addToCache(emoji)
}
return emoji, err
}
func (es LocalCacheEmojiStore) GetByName(name string, allowFromCache bool) (*model.Emoji, *model.AppError) {
if id, ok := model.GetSystemEmojiId(name); ok {
return es.Get(id, allowFromCache)
}
if allowFromCache {
if emoji, ok := es.getFromCacheByName(name); ok {
return emoji, nil
}
}
emoji, err := es.EmojiStore.GetByName(name, allowFromCache)
if allowFromCache && err == nil {
es.addToCache(emoji)
}
return emoji, err
}
func (es LocalCacheEmojiStore) Delete(emoji *model.Emoji, time int64) *model.AppError {
err := es.EmojiStore.Delete(emoji, time)
if err == nil {
es.removeFromCache(emoji)
}
return err
}
func (es LocalCacheEmojiStore) addToCache(emoji *model.Emoji) {
es.rootStore.doStandardAddToCache(es.rootStore.emojiCacheById, emoji.Id, emoji)
es.rootStore.doStandardAddToCache(es.rootStore.emojiIdCacheByName, emoji.Name, emoji.Id)
}
func (es LocalCacheEmojiStore) getFromCacheById(id string) (*model.Emoji, bool) {
if emoji := es.rootStore.doStandardReadCache(es.rootStore.emojiCacheById, id); emoji != nil {
return emoji.(*model.Emoji), true
}
return nil, false
}
func (es LocalCacheEmojiStore) getFromCacheByName(name string) (*model.Emoji, bool) {
if emojiId := es.rootStore.doStandardReadCache(es.rootStore.emojiIdCacheByName, name); emojiId != nil {
return es.getFromCacheById(emojiId.(string))
}
return nil, false
}
func (es LocalCacheEmojiStore) removeFromCache(emoji *model.Emoji) {
es.rootStore.doInvalidateCacheCluster(es.rootStore.emojiCacheById, emoji.Id)
es.rootStore.doInvalidateCacheCluster(es.rootStore.emojiIdCacheByName, emoji.Name)
}

136
store/localcachelayer/emoji_layer_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,136 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package localcachelayer
import (
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store/storetest"
"github.com/mattermost/mattermost-server/store/storetest/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEmojiStore(t *testing.T) {
StoreTest(t, storetest.TestEmojiStore)
}
func TestEmojiStoreCache(t *testing.T) {
fakeEmoji := model.Emoji{Id: "123", Name: "name123"}
t.Run("first call by id not cached, second cached and returning same data", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
emoji, err := cachedStore.Emoji().Get("123", true)
require.Nil(t, err)
assert.Equal(t, emoji, &fakeEmoji)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 1)
emoji, err = cachedStore.Emoji().Get("123", true)
require.Nil(t, err)
assert.Equal(t, emoji, &fakeEmoji)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 1)
})
t.Run("first call by name not cached, second cached and returning same data", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
emoji, err := cachedStore.Emoji().GetByName("name123", true)
require.Nil(t, err)
assert.Equal(t, emoji, &fakeEmoji)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 1)
emoji, err = cachedStore.Emoji().GetByName("name123", true)
require.Nil(t, err)
assert.Equal(t, emoji, &fakeEmoji)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 1)
})
t.Run("first call by id not cached, second force no cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Emoji().Get("123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 1)
cachedStore.Emoji().Get("123", false)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 2)
})
t.Run("first call by name not cached, second force no cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Emoji().GetByName("name123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 1)
cachedStore.Emoji().GetByName("name123", false)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 2)
})
t.Run("first call by id force no cached, second not cached, third cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Emoji().Get("123", false)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 1)
cachedStore.Emoji().Get("123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 2)
cachedStore.Emoji().Get("123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 2)
})
t.Run("first call by id force no cached, second not cached, third cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Emoji().GetByName("name123", false)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 1)
cachedStore.Emoji().GetByName("name123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 2)
cachedStore.Emoji().GetByName("name123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 2)
})
t.Run("first call by id, second call by name cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Emoji().Get("123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 1)
cachedStore.Emoji().GetByName("name123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 0)
})
t.Run("first call by name, second call by id cached", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Emoji().GetByName("name123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 1)
cachedStore.Emoji().Get("123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 0)
})
t.Run("first call by id not cached, invalidate, and then not cached again", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Emoji().Get("123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 1)
cachedStore.Emoji().Delete(&fakeEmoji, 0)
cachedStore.Emoji().Get("123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "Get", 2)
})
t.Run("first call by name not cached, invalidate, and then not cached again", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Emoji().GetByName("name123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 1)
cachedStore.Emoji().Delete(&fakeEmoji, 0)
cachedStore.Emoji().GetByName("name123", true)
mockStore.Emoji().(*mocks.EmojiStore).AssertNumberOfCalls(t, "GetByName", 2)
})
}

Просмотреть файл

@@ -20,6 +20,12 @@ const (
SCHEME_CACHE_SIZE = 20000 SCHEME_CACHE_SIZE = 20000
SCHEME_CACHE_SEC = 30 * 60 SCHEME_CACHE_SEC = 30 * 60
EMOJI_CACHE_SIZE = 5000
EMOJI_CACHE_SEC = 30 * 60
CHANNEL_MEMBERS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
CHANNEL_MEMBERS_COUNTS_CACHE_SEC = 30 * 60
CLEAR_CACHE_MESSAGE_DATA = "" CLEAR_CACHE_MESSAGE_DATA = ""
) )
@@ -33,6 +39,11 @@ type LocalCacheStore struct {
roleCache *utils.Cache roleCache *utils.Cache
scheme LocalCacheSchemeStore scheme LocalCacheSchemeStore
schemeCache *utils.Cache schemeCache *utils.Cache
emoji LocalCacheEmojiStore
emojiCacheById *utils.Cache
emojiIdCacheByName *utils.Cache
channel LocalCacheChannelStore
channelMemberCountsCache *utils.Cache
} }
func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) LocalCacheStore { func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) LocalCacheStore {
@@ -47,11 +58,19 @@ func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterf
localCacheStore.role = LocalCacheRoleStore{RoleStore: baseStore.Role(), rootStore: &localCacheStore} localCacheStore.role = LocalCacheRoleStore{RoleStore: baseStore.Role(), rootStore: &localCacheStore}
localCacheStore.schemeCache = utils.NewLruWithParams(SCHEME_CACHE_SIZE, "Scheme", SCHEME_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES) localCacheStore.schemeCache = utils.NewLruWithParams(SCHEME_CACHE_SIZE, "Scheme", SCHEME_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES)
localCacheStore.scheme = LocalCacheSchemeStore{SchemeStore: baseStore.Scheme(), rootStore: &localCacheStore} localCacheStore.scheme = LocalCacheSchemeStore{SchemeStore: baseStore.Scheme(), rootStore: &localCacheStore}
localCacheStore.emojiCacheById = utils.NewLruWithParams(EMOJI_CACHE_SIZE, "EmojiById", EMOJI_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_BY_ID)
localCacheStore.emojiIdCacheByName = utils.NewLruWithParams(EMOJI_CACHE_SIZE, "EmojiByName", EMOJI_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_ID_BY_NAME)
localCacheStore.emoji = LocalCacheEmojiStore{EmojiStore: baseStore.Emoji(), rootStore: &localCacheStore}
localCacheStore.channelMemberCountsCache = utils.NewLruWithParams(CHANNEL_MEMBERS_COUNTS_CACHE_SIZE, "ChannelMemberCounts", CHANNEL_MEMBERS_COUNTS_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBER_COUNTS)
localCacheStore.channel = LocalCacheChannelStore{ChannelStore: baseStore.Channel(), rootStore: &localCacheStore}
if cluster != nil { if cluster != nil {
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, localCacheStore.reaction.handleClusterInvalidateReaction) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, localCacheStore.reaction.handleClusterInvalidateReaction)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, localCacheStore.role.handleClusterInvalidateRole) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, localCacheStore.role.handleClusterInvalidateRole)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES, localCacheStore.scheme.handleClusterInvalidateScheme) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES, localCacheStore.scheme.handleClusterInvalidateScheme)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_BY_ID, localCacheStore.emoji.handleClusterInvalidateEmojiById)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_ID_BY_NAME, localCacheStore.emoji.handleClusterInvalidateEmojiIdByName)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBER_COUNTS, localCacheStore.channel.handleClusterInvalidateChannelMemberCounts)
} }
return localCacheStore return localCacheStore
} }
@@ -68,6 +87,14 @@ func (s LocalCacheStore) Scheme() store.SchemeStore {
return s.scheme return s.scheme
} }
func (s LocalCacheStore) Emoji() store.EmojiStore {
return s.emoji
}
func (s LocalCacheStore) Channel() store.ChannelStore {
return s.channel
}
func (s LocalCacheStore) DropAllTables() { func (s LocalCacheStore) DropAllTables() {
s.Invalidate() s.Invalidate()
s.Store.DropAllTables() s.Store.DropAllTables()
@@ -118,4 +145,7 @@ func (s *LocalCacheStore) doClearCacheCluster(cache *utils.Cache) {
func (s *LocalCacheStore) Invalidate() { func (s *LocalCacheStore) Invalidate() {
s.doClearCacheCluster(s.reactionCache) s.doClearCacheCluster(s.reactionCache)
s.doClearCacheCluster(s.emojiCacheById)
s.doClearCacheCluster(s.emojiIdCacheByName)
s.doClearCacheCluster(s.channelMemberCountsCache)
} }

Просмотреть файл

@@ -41,6 +41,22 @@ func getMockStore() *mocks.Store {
mockSchemesStore.On("PermanentDeleteAll").Return(nil) mockSchemesStore.On("PermanentDeleteAll").Return(nil)
mockStore.On("Scheme").Return(&mockSchemesStore) mockStore.On("Scheme").Return(&mockSchemesStore)
fakeEmoji := model.Emoji{Id: "123", Name: "name123"}
mockEmojiStore := mocks.EmojiStore{}
mockEmojiStore.On("Get", "123", true).Return(&fakeEmoji, nil)
mockEmojiStore.On("Get", "123", false).Return(&fakeEmoji, nil)
mockEmojiStore.On("GetByName", "name123", true).Return(&fakeEmoji, nil)
mockEmojiStore.On("GetByName", "name123", false).Return(&fakeEmoji, nil)
mockEmojiStore.On("Delete", &fakeEmoji, int64(0)).Return(nil)
mockStore.On("Emoji").Return(&mockEmojiStore)
mockCount := int64(10)
mockChannelStore := mocks.ChannelStore{}
mockChannelStore.On("ClearCaches").Return()
mockChannelStore.On("GetMemberCount", "id", true).Return(mockCount, nil)
mockChannelStore.On("GetMemberCount", "id", false).Return(mockCount, nil)
mockStore.On("Channel").Return(&mockChannelStore)
return &mockStore return &mockStore
} }

Просмотреть файл

@@ -29,9 +29,6 @@ const (
ALL_CHANNEL_MEMBERS_NOTIFY_PROPS_FOR_CHANNEL_CACHE_SIZE = model.SESSION_CACHE_SIZE ALL_CHANNEL_MEMBERS_NOTIFY_PROPS_FOR_CHANNEL_CACHE_SIZE = model.SESSION_CACHE_SIZE
ALL_CHANNEL_MEMBERS_NOTIFY_PROPS_FOR_CHANNEL_CACHE_SEC = 1800 // 30 mins ALL_CHANNEL_MEMBERS_NOTIFY_PROPS_FOR_CHANNEL_CACHE_SEC = 1800 // 30 mins
CHANNEL_MEMBERS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
CHANNEL_MEMBERS_COUNTS_CACHE_SEC = 1800 // 30 mins
CHANNEL_GUESTS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE CHANNEL_GUESTS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
CHANNEL_GUESTS_COUNTS_CACHE_SEC = 1800 // 30 mins CHANNEL_GUESTS_COUNTS_CACHE_SEC = 1800 // 30 mins
@@ -283,7 +280,6 @@ type publicChannel struct {
Purpose string `json:"purpose"` Purpose string `json:"purpose"`
} }
var channelMemberCountsCache = utils.NewLru(CHANNEL_MEMBERS_COUNTS_CACHE_SIZE)
var channelPinnedPostCountsCache = utils.NewLru(CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SIZE) var channelPinnedPostCountsCache = utils.NewLru(CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SIZE)
var channelGuestCountsCache = utils.NewLru(CHANNEL_GUESTS_COUNTS_CACHE_SIZE) var channelGuestCountsCache = utils.NewLru(CHANNEL_GUESTS_COUNTS_CACHE_SIZE)
var allChannelMembersForUserCache = utils.NewLru(ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE) var allChannelMembersForUserCache = utils.NewLru(ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE)
@@ -292,7 +288,6 @@ var channelCache = utils.NewLru(model.CHANNEL_CACHE_SIZE)
var channelByNameCache = utils.NewLru(model.CHANNEL_CACHE_SIZE) var channelByNameCache = utils.NewLru(model.CHANNEL_CACHE_SIZE)
func (s SqlChannelStore) ClearCaches() { func (s SqlChannelStore) ClearCaches() {
channelMemberCountsCache.Purge()
channelPinnedPostCountsCache.Purge() channelPinnedPostCountsCache.Purge()
channelGuestCountsCache.Purge() channelGuestCountsCache.Purge()
allChannelMembersForUserCache.Purge() allChannelMembersForUserCache.Purge()
@@ -301,7 +296,6 @@ func (s SqlChannelStore) ClearCaches() {
channelByNameCache.Purge() channelByNameCache.Purge()
if s.metrics != nil { if s.metrics != nil {
s.metrics.IncrementMemCacheInvalidationCounter("Channel Member Counts - Purge")
s.metrics.IncrementMemCacheInvalidationCounter("Channel Pinned Post Counts - Purge") s.metrics.IncrementMemCacheInvalidationCounter("Channel Pinned Post Counts - Purge")
s.metrics.IncrementMemCacheInvalidationCounter("All Channel Members for User - Purge") s.metrics.IncrementMemCacheInvalidationCounter("All Channel Members for User - Purge")
s.metrics.IncrementMemCacheInvalidationCounter("All Channel Members Notify Props for Channel - Purge") s.metrics.IncrementMemCacheInvalidationCounter("All Channel Members Notify Props for Channel - Purge")
@@ -1585,46 +1579,14 @@ func (s SqlChannelStore) GetAllChannelMembersNotifyPropsForChannel(channelId str
} }
func (s SqlChannelStore) InvalidateMemberCount(channelId string) { func (s SqlChannelStore) InvalidateMemberCount(channelId string) {
channelMemberCountsCache.Remove(channelId)
if s.metrics != nil {
s.metrics.IncrementMemCacheInvalidationCounter("Channel Member Counts - Remove by ChannelId")
}
} }
func (s SqlChannelStore) GetMemberCountFromCache(channelId string) int64 { func (s SqlChannelStore) GetMemberCountFromCache(channelId string) int64 {
if cacheItem, ok := channelMemberCountsCache.Get(channelId); ok { count, _ := s.GetMemberCount(channelId, true)
if s.metrics != nil {
s.metrics.IncrementMemCacheHitCounter("Channel Member Counts")
}
return cacheItem.(int64)
}
if s.metrics != nil {
s.metrics.IncrementMemCacheMissCounter("Channel Member Counts")
}
count, err := s.GetMemberCount(channelId, true)
if err != nil {
return 0
}
return count return count
} }
func (s SqlChannelStore) GetMemberCount(channelId string, allowFromCache bool) (int64, *model.AppError) { func (s SqlChannelStore) GetMemberCount(channelId string, allowFromCache bool) (int64, *model.AppError) {
if allowFromCache {
if cacheItem, ok := channelMemberCountsCache.Get(channelId); ok {
if s.metrics != nil {
s.metrics.IncrementMemCacheHitCounter("Channel Member Counts")
}
return cacheItem.(int64), nil
}
}
if s.metrics != nil {
s.metrics.IncrementMemCacheMissCounter("Channel Member Counts")
}
count, err := s.GetReplica().SelectInt(` count, err := s.GetReplica().SelectInt(`
SELECT SELECT
count(*) count(*)
@@ -1639,10 +1601,6 @@ func (s SqlChannelStore) GetMemberCount(channelId string, allowFromCache bool) (
return 0, model.NewAppError("SqlChannelStore.GetMemberCount", "store.sql_channel.get_member_count.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError) return 0, model.NewAppError("SqlChannelStore.GetMemberCount", "store.sql_channel.get_member_count.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
} }
if allowFromCache {
channelMemberCountsCache.AddWithExpiresInSecs(channelId, count, CHANNEL_MEMBERS_COUNTS_CACHE_SEC)
}
return count, nil return count, nil
} }

Просмотреть файл

@@ -216,6 +216,7 @@ func (s SqlComplianceStore) MessageExport(after int64, limit int) ([]*model.Mess
Posts.DeleteAt AS PostDeleteAt, Posts.DeleteAt AS PostDeleteAt,
Posts.Message AS PostMessage, Posts.Message AS PostMessage,
Posts.Type AS PostType, Posts.Type AS PostType,
Posts.Props AS PostProps,
Posts.OriginalId AS PostOriginalId, Posts.OriginalId AS PostOriginalId,
Posts.RootId AS PostRootId, Posts.RootId AS PostRootId,
Posts.Props AS PostProps, Posts.Props AS PostProps,
@@ -243,7 +244,7 @@ func (s SqlComplianceStore) MessageExport(after int64, limit int) ([]*model.Mess
LEFT JOIN Bots ON Bots.UserId = Posts.UserId LEFT JOIN Bots ON Bots.UserId = Posts.UserId
WHERE WHERE
(Posts.CreateAt > :StartTime OR Posts.EditAt > :StartTime OR Posts.DeleteAt > :StartTime) AND (Posts.CreateAt > :StartTime OR Posts.EditAt > :StartTime OR Posts.DeleteAt > :StartTime) AND
Posts.Type = '' Posts.Type NOT LIKE 'system_%'
ORDER BY PostUpdateAt ORDER BY PostUpdateAt
LIMIT :Limit` LIMIT :Limit`

Просмотреть файл

@@ -11,17 +11,8 @@ import (
"github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/utils"
) )
const (
EMOJI_CACHE_SIZE = 5000
EMOJI_CACHE_SEC = 1800 // 30 mins
)
var emojiCacheById = utils.NewLru(EMOJI_CACHE_SIZE)
var emojiIdCacheByName = utils.NewLru(EMOJI_CACHE_SIZE)
type SqlEmojiStore struct { type SqlEmojiStore struct {
SqlStore SqlStore
metrics einterfaces.MetricsInterface metrics einterfaces.MetricsInterface
@@ -66,26 +57,10 @@ func (es SqlEmojiStore) Save(emoji *model.Emoji) (*model.Emoji, *model.AppError)
} }
func (es SqlEmojiStore) Get(id string, allowFromCache bool) (*model.Emoji, *model.AppError) { func (es SqlEmojiStore) Get(id string, allowFromCache bool) (*model.Emoji, *model.AppError) {
if allowFromCache {
if emoji, ok := es.getFromCacheById(id); ok {
return emoji, nil
}
}
return es.getBy("Id", id, allowFromCache) return es.getBy("Id", id, allowFromCache)
} }
func (es SqlEmojiStore) GetByName(name string, allowFromCache bool) (*model.Emoji, *model.AppError) { func (es SqlEmojiStore) GetByName(name string, allowFromCache bool) (*model.Emoji, *model.AppError) {
if id, ok := model.GetSystemEmojiId(name); ok {
return es.Get(id, allowFromCache)
}
if allowFromCache {
if emoji, ok := es.getFromCacheByName(name); ok {
return emoji, nil
}
}
return es.getBy("Name", name, allowFromCache) return es.getBy("Name", name, allowFromCache)
} }
@@ -139,8 +114,6 @@ func (es SqlEmojiStore) Delete(emoji *model.Emoji, time int64) *model.AppError {
return model.NewAppError("SqlEmojiStore.Delete", "store.sql_emoji.delete.no_results", nil, "id="+emoji.Id, http.StatusBadRequest) return model.NewAppError("SqlEmojiStore.Delete", "store.sql_emoji.delete.no_results", nil, "id="+emoji.Id, http.StatusBadRequest)
} }
es.removeFromCache(emoji)
return nil return nil
} }
@@ -193,51 +166,5 @@ func (es SqlEmojiStore) getBy(what string, key interface{}, addToCache bool) (*m
return nil, model.NewAppError("SqlEmojiStore.GetByName", "store.sql_emoji.get.app_error", nil, "key="+fmt.Sprintf("%v", key)+", "+err.Error(), status) return nil, model.NewAppError("SqlEmojiStore.GetByName", "store.sql_emoji.get.app_error", nil, "key="+fmt.Sprintf("%v", key)+", "+err.Error(), status)
} }
if addToCache {
es.addToCache(emoji)
}
return emoji, nil return emoji, nil
} }
func (es SqlEmojiStore) addToCache(emoji *model.Emoji) {
emojiCacheById.AddWithExpiresInSecs(emoji.Id, emoji, EMOJI_CACHE_SEC)
emojiIdCacheByName.AddWithExpiresInSecs(emoji.Name, emoji.Id, EMOJI_CACHE_SEC)
}
func (es SqlEmojiStore) getFromCacheById(id string) (*model.Emoji, bool) {
if cacheItem, ok := emojiCacheById.Get(id); ok {
es.incrementMemCacheHitCounter("Emoji")
return cacheItem.(*model.Emoji), true
}
es.incrementMemCacheMissCounter("Emoji")
return nil, false
}
func (es SqlEmojiStore) getFromCacheByName(name string) (*model.Emoji, bool) {
if id, ok := emojiIdCacheByName.Get(name); ok {
return es.getFromCacheById(id.(string))
}
es.incrementMemCacheMissCounter("Emoji")
return nil, false
}
func (es SqlEmojiStore) incrementMemCacheHitCounter(cache string) {
if es.metrics == nil {
return
}
es.metrics.IncrementMemCacheHitCounter(cache)
}
func (es SqlEmojiStore) incrementMemCacheMissCounter(cache string) {
if es.metrics == nil {
return
}
es.metrics.IncrementMemCacheMissCounter(cache)
}
func (es SqlEmojiStore) removeFromCache(emoji *model.Emoji) {
emojiCacheById.Remove(emoji.Id)
emojiIdCacheByName.Remove(emoji.Name)
}

Просмотреть файл

@@ -18,7 +18,8 @@ import (
) )
const ( const (
CURRENT_SCHEMA_VERSION = VERSION_5_16_0 CURRENT_SCHEMA_VERSION = VERSION_5_17_0
VERSION_5_17_0 = "5.17.0"
VERSION_5_16_0 = "5.16.0" VERSION_5_16_0 = "5.16.0"
VERSION_5_15_0 = "5.15.0" VERSION_5_15_0 = "5.15.0"
VERSION_5_14_0 = "5.14.0" VERSION_5_14_0 = "5.14.0"
@@ -163,6 +164,7 @@ func upgradeDatabase(sqlStore SqlStore, currentModelVersionString string) error
upgradeDatabaseToVersion514(sqlStore) upgradeDatabaseToVersion514(sqlStore)
upgradeDatabaseToVersion515(sqlStore) upgradeDatabaseToVersion515(sqlStore)
upgradeDatabaseToVersion516(sqlStore) upgradeDatabaseToVersion516(sqlStore)
upgradeDatabaseToVersion517(sqlStore)
return nil return nil
} }
@@ -721,3 +723,9 @@ func upgradeDatabaseToVersion516(sqlStore SqlStore) {
sqlStore.CreateIndexIfNotExists("idx_groupchannels_channelid", "GroupChannels", "ChannelId") sqlStore.CreateIndexIfNotExists("idx_groupchannels_channelid", "GroupChannels", "ChannelId")
} }
} }
func upgradeDatabaseToVersion517(sqlStore SqlStore) {
if shouldPerformUpgrade(sqlStore, VERSION_5_16_0, VERSION_5_17_0) {
saveSchemaVersion(sqlStore, VERSION_5_17_0)
}
}

Просмотреть файл

@@ -141,6 +141,40 @@ func (us SqlUserStore) Save(user *model.User) (*model.User, *model.AppError) {
return user, nil return user, nil
} }
func (us SqlUserStore) DeactivateGuests() ([]string, *model.AppError) {
curTime := model.GetMillis()
updateQuery := us.getQueryBuilder().Update("Users").
Set("UpdateAt", curTime).
Set("DeleteAt", curTime).
Where(sq.Eq{"Roles": "system_guest"}).
Where(sq.Eq{"DeleteAt": 0})
queryString, args, err := updateQuery.ToSql()
if err != nil {
return nil, model.NewAppError("SqlUserStore.UpdateActiveForMultipleUsers", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
_, err = us.GetMaster().Exec(queryString, args...)
if err != nil {
return nil, model.NewAppError("SqlUserStore.UpdateActiveForMultipleUsers", "store.sql_user.update_active_for_multiple_users.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
}
selectQuery := us.getQueryBuilder().Select("Id").From("Users").Where(sq.Eq{"DeleteAt": curTime})
queryString, args, err = selectQuery.ToSql()
if err != nil {
return nil, model.NewAppError("SqlUserStore.UpdateActiveForMultipleUsers", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
userIds := []string{}
_, err = us.GetMaster().Select(&userIds, queryString, args...)
if err != nil {
return nil, model.NewAppError("SqlUserStore.UpdateActiveForMultipleUsers", "store.sql_user.update_active_for_multiple_users.getting_changed_users.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return userIds, nil
}
func (us SqlUserStore) Update(user *model.User, trustedUpdateData bool) (*model.UserUpdate, *model.AppError) { func (us SqlUserStore) Update(user *model.User, trustedUpdateData bool) (*model.UserUpdate, *model.AppError) {
user.PreUpdate() user.PreUpdate()

Просмотреть файл

@@ -301,6 +301,7 @@ type UserStore interface {
GetChannelGroupUsers(channelID string) ([]*model.User, *model.AppError) GetChannelGroupUsers(channelID string) ([]*model.User, *model.AppError)
PromoteGuestToUser(userID string) *model.AppError PromoteGuestToUser(userID string) *model.AppError
DemoteUserToGuest(userID string) *model.AppError DemoteUserToGuest(userID string) *model.AppError
DeactivateGuests() ([]string, *model.AppError)
} }
type BotStore interface { type BotStore interface {

Просмотреть файл

@@ -21,7 +21,6 @@ func TestEmojiStore(t *testing.T, ss store.Store) {
t.Run("EmojiGetMultipleByName", func(t *testing.T) { testEmojiGetMultipleByName(t, ss) }) t.Run("EmojiGetMultipleByName", func(t *testing.T) { testEmojiGetMultipleByName(t, ss) })
t.Run("EmojiGetList", func(t *testing.T) { testEmojiGetList(t, ss) }) t.Run("EmojiGetList", func(t *testing.T) { testEmojiGetList(t, ss) })
t.Run("EmojiSearch", func(t *testing.T) { testEmojiSearch(t, ss) }) t.Run("EmojiSearch", func(t *testing.T) { testEmojiSearch(t, ss) })
t.Run("EmojiCaching", func(t *testing.T) { testEmojiCaching(t, ss) })
} }
func testEmojiSaveDelete(t *testing.T, ss store.Store) { func testEmojiSaveDelete(t *testing.T, ss store.Store) {
@@ -91,61 +90,6 @@ func testEmojiGet(t *testing.T, ss store.Store) {
} }
} }
func testEmojiCaching(t *testing.T, ss store.Store) {
emojis := make([]*model.Emoji, 3)
for i := range emojis {
emojis[i] = &model.Emoji{
CreatorId: model.NewId(),
Name: model.NewId(),
}
}
for _, emoji := range emojis {
_, err := ss.Emoji().Save(emoji)
require.Nil(t, err)
}
defer func() {
for _, emoji := range emojis {
err := ss.Emoji().Delete(emoji, time.Now().Unix())
require.Nil(t, err)
}
}()
var retrievedEmoji *model.Emoji
var cachedEmoji *model.Emoji
var err *model.AppError
for _, emoji := range emojis {
cachedEmoji, err = ss.Emoji().Get(emoji.Id, true)
assert.Nilf(t, err, "should be able to retrieve emoji with id %v", emoji.Id)
retrievedEmoji, err = ss.Emoji().Get(emoji.Id, false)
if assert.Nilf(t, err, "should be able to retrieve emoji with id %v", emoji.Id) {
assert.Falsef(t, retrievedEmoji == cachedEmoji, "should not be the same as cached with id %v", emoji.Id)
}
retrievedEmoji, err = ss.Emoji().Get(emoji.Id, true)
if assert.Nilf(t, err, "should be able to retrieve emoji with id %v", emoji.Id) {
assert.Truef(t, retrievedEmoji == cachedEmoji, "should be the cached emoji with id %v", emoji.Id)
}
retrievedEmoji, err = ss.Emoji().GetByName(emoji.Name, false)
if assert.Nilf(t, err, "should be able to retrieve emoji with name %v", emoji.Name) {
assert.Falsef(t, retrievedEmoji == cachedEmoji, "should not be the same as cached with name %v", emoji.Name)
}
retrievedEmoji, _ = ss.Emoji().GetByName(emoji.Name, true)
if assert.Nilf(t, err, "should be able to retrieve emoji with name %v", emoji.Name) {
assert.Truef(t, retrievedEmoji == cachedEmoji, "should be the cached emoji with name %v", emoji.Name)
}
}
_, err = ss.Emoji().Get(model.NewId(), false)
assert.NotNilf(t, err, "should not retrieve emoji with unsaved ID")
_, err = ss.Emoji().GetByName(model.NewId(), false)
assert.NotNilf(t, err, "should not retrieve emoji with unsaved name")
}
func testEmojiGetByName(t *testing.T, ss store.Store) { func testEmojiGetByName(t *testing.T, ss store.Store) {
emojis := []model.Emoji{ emojis := []model.Emoji{
{ {

Просмотреть файл

@@ -128,6 +128,31 @@ func (_m *UserStore) Count(options model.UserCountOptions) (int64, *model.AppErr
return r0, r1 return r0, r1
} }
// DeactivateGuests provides a mock function with given fields:
func (_m *UserStore) DeactivateGuests() ([]string, *model.AppError) {
ret := _m.Called()
var r0 []string
if rf, ok := ret.Get(0).(func() []string); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func() *model.AppError); ok {
r1 = rf()
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// DemoteUserToGuest provides a mock function with given fields: userID // DemoteUserToGuest provides a mock function with given fields: userID
func (_m *UserStore) DemoteUserToGuest(userID string) *model.AppError { func (_m *UserStore) DemoteUserToGuest(userID string) *model.AppError {
ret := _m.Called(userID) ret := _m.Called(userID)

Просмотреть файл

@@ -80,6 +80,7 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("GetChannelGroupUsers", func(t *testing.T) { testUserStoreGetChannelGroupUsers(t, ss) }) t.Run("GetChannelGroupUsers", func(t *testing.T) { testUserStoreGetChannelGroupUsers(t, ss) })
t.Run("PromoteGuestToUser", func(t *testing.T) { testUserStorePromoteGuestToUser(t, ss) }) t.Run("PromoteGuestToUser", func(t *testing.T) { testUserStorePromoteGuestToUser(t, ss) })
t.Run("DemoteUserToGuest", func(t *testing.T) { testUserStoreDemoteUserToGuest(t, ss) }) t.Run("DemoteUserToGuest", func(t *testing.T) { testUserStoreDemoteUserToGuest(t, ss) })
t.Run("DeactivateGuests", func(t *testing.T) { testDeactivateGuests(t, ss) })
t.Run("ResetLastPictureUpdate", func(t *testing.T) { testUserStoreResetLastPictureUpdate(t, ss) }) t.Run("ResetLastPictureUpdate", func(t *testing.T) { testUserStoreResetLastPictureUpdate(t, ss) })
} }
@@ -4206,6 +4207,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
Roles: "system_user", Roles: "system_user",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999)
@@ -4251,6 +4253,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
Roles: "system_user system_admin", Roles: "system_user system_admin",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999)
@@ -4295,6 +4298,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
Roles: "system_guest", Roles: "system_guest",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
err = ss.User().PromoteGuestToUser(user.Id) err = ss.User().PromoteGuestToUser(user.Id)
assert.Nil(t, err) assert.Nil(t, err)
@@ -4315,6 +4319,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
Roles: "system_guest", Roles: "system_guest",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999)
@@ -4344,6 +4349,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
Roles: "system_guest", Roles: "system_guest",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999)
@@ -4388,6 +4394,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
Roles: "system_guest custom_role", Roles: "system_guest custom_role",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999)
@@ -4432,6 +4439,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
Roles: "system_guest", Roles: "system_guest",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user1.Id)) }()
teamId1 := model.NewId() teamId1 := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: user1.Id, SchemeGuest: true, SchemeUser: false}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: user1.Id, SchemeGuest: true, SchemeUser: false}, 999)
@@ -4459,6 +4467,7 @@ func testUserStorePromoteGuestToUser(t *testing.T, ss store.Store) {
Roles: "system_guest", Roles: "system_guest",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user2.Id)) }()
teamId2 := model.NewId() teamId2 := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId2, UserId: user2.Id, SchemeGuest: true, SchemeUser: false}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId2, UserId: user2.Id, SchemeGuest: true, SchemeUser: false}, 999)
@@ -4513,6 +4522,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
Roles: "system_guest", Roles: "system_guest",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999)
@@ -4558,6 +4568,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
Roles: "system_user system_admin", Roles: "system_user system_admin",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: true, SchemeUser: false}, 999)
@@ -4602,6 +4613,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
Roles: "system_user", Roles: "system_user",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
err = ss.User().DemoteUserToGuest(user.Id) err = ss.User().DemoteUserToGuest(user.Id)
assert.Nil(t, err) assert.Nil(t, err)
@@ -4622,6 +4634,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
Roles: "system_user", Roles: "system_user",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999)
@@ -4651,6 +4664,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
Roles: "system_user", Roles: "system_user",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999)
@@ -4695,6 +4709,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
Roles: "system_user custom_role", Roles: "system_user custom_role",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user.Id)) }()
teamId := model.NewId() teamId := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id, SchemeGuest: false, SchemeUser: true}, 999)
@@ -4739,6 +4754,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
Roles: "system_user", Roles: "system_user",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user1.Id)) }()
teamId1 := model.NewId() teamId1 := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: user1.Id, SchemeGuest: false, SchemeUser: true}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: user1.Id, SchemeGuest: false, SchemeUser: true}, 999)
@@ -4766,6 +4782,7 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
Roles: "system_user", Roles: "system_user",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(user2.Id)) }()
teamId2 := model.NewId() teamId2 := model.NewId()
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId2, UserId: user2.Id, SchemeGuest: false, SchemeUser: true}, 999) _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId2, UserId: user2.Id, SchemeGuest: false, SchemeUser: true}, 999)
@@ -4806,6 +4823,84 @@ func testUserStoreDemoteUserToGuest(t *testing.T, ss store.Store) {
}) })
} }
func testDeactivateGuests(t *testing.T, ss store.Store) {
// create users
t.Run("Must disable all guests and no regular user or already deactivated users", func(t *testing.T) {
guest1Random := model.NewId()
guest1, err := ss.User().Save(&model.User{
Email: guest1Random + "@test.com",
Username: "un_" + guest1Random,
Nickname: "nn_" + guest1Random,
FirstName: "f_" + guest1Random,
LastName: "l_" + guest1Random,
Password: "Password1",
Roles: "system_guest",
})
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(guest1.Id)) }()
guest2Random := model.NewId()
guest2, err := ss.User().Save(&model.User{
Email: guest2Random + "@test.com",
Username: "un_" + guest2Random,
Nickname: "nn_" + guest2Random,
FirstName: "f_" + guest2Random,
LastName: "l_" + guest2Random,
Password: "Password1",
Roles: "system_guest",
})
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(guest2.Id)) }()
guest3Random := model.NewId()
guest3, err := ss.User().Save(&model.User{
Email: guest3Random + "@test.com",
Username: "un_" + guest3Random,
Nickname: "nn_" + guest3Random,
FirstName: "f_" + guest3Random,
LastName: "l_" + guest3Random,
Password: "Password1",
Roles: "system_guest",
DeleteAt: 10,
})
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(guest3.Id)) }()
regularUserRandom := model.NewId()
regularUser, err := ss.User().Save(&model.User{
Email: regularUserRandom + "@test.com",
Username: "un_" + regularUserRandom,
Nickname: "nn_" + regularUserRandom,
FirstName: "f_" + regularUserRandom,
LastName: "l_" + regularUserRandom,
Password: "Password1",
Roles: "system_user",
})
require.Nil(t, err)
defer func() { require.Nil(t, ss.User().PermanentDelete(regularUser.Id)) }()
ids, err := ss.User().DeactivateGuests()
require.Nil(t, err)
assert.ElementsMatch(t, []string{guest1.Id, guest2.Id}, ids)
u, err := ss.User().Get(guest1.Id)
require.Nil(t, err)
assert.NotEqual(t, u.DeleteAt, int64(0))
u, err = ss.User().Get(guest2.Id)
require.Nil(t, err)
assert.NotEqual(t, u.DeleteAt, int64(0))
u, err = ss.User().Get(guest3.Id)
require.Nil(t, err)
assert.Equal(t, u.DeleteAt, int64(10))
u, err = ss.User().Get(regularUser.Id)
require.Nil(t, err)
assert.Equal(t, u.DeleteAt, int64(0))
})
}
func testUserStoreResetLastPictureUpdate(t *testing.T, ss store.Store) { func testUserStoreResetLastPictureUpdate(t *testing.T, ss store.Store) {
u1 := &model.User{} u1 := &model.User{}
u1.Email = MakeEmail() u1.Email = MakeEmail()

Просмотреть файл

@@ -2,6 +2,7 @@
<li class="message"> <li class="message">
<span class="sent_time">{{.Props.SentTime}}</span> <span class="sent_time">{{.Props.SentTime}}</span>
<span class="username">@{{.Props.Username}}</span> <span class="username">@{{.Props.Username}}</span>
<span class="postusername">{{.Props.PostUsername}}</span>
<span class="usertype">{{.Props.UserType}}</span> <span class="usertype">{{.Props.UserType}}</span>
<span class="email">({{.Props.Email}}):</span> <span class="email">({{.Props.Email}}):</span>
<span class="message">{{.Props.Message}}</span> <span class="message">{{.Props.Message}}</span>

41
tests/development-public-key.asc Обычный файл
Просмотреть файл

@@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBF2gen8BDADKQObdPa6PagvYYMHNGIswCU9mVjOxr5g6niGQ/AxMW7AaHpkk
16/oAzJ+DSyJRRgJMlFbN0iKBrZ6pi1pO5eS4l1CWW3eATr+32gW40SuS/sgzVrS
OYPocqtsC9XWHK2j/UFbaI7aivnUYKIuBzhAWdcUYggjd1qgmM18zWYkuV1Jnywu
Xue3Vsc/pLGqybG/EkBmZHRktr4fNn2xEjmKnUKp28vMF4Pz5e8/2qklSsc9UVl5
avkex+glOeJSWF3L7S5CmHAWVgQNwKoJrvq7pKOUsZqrHScjyujeKp1Y6cUZdcBF
8bsF1I+J2RxQFqcC6O08x29948P8UkOv4/FpUGYhx6tcqnQ0PdT4fjPslRapvdZo
RiGdlvJLKUvhfRF0cgPxflde7M42cV5saOXKyaF2hPJi/SsFkTVSnyCyixdr8z+M
QMIcgtrQ26ig9s5J0h2j3y9sgvvTh1nxE/XWOlrXjCVohNSRWZBjX1PEd2dAk4ZK
OEB5YcST61kbhd0AEQEAAbRBTWF0dGVybW9zdCBJbmMuIChQbHVnaW4gc2lnbmlu
ZyBSU0EgZGV2LWtleSkgPGFsaUBtYXR0ZXJtb3N0LmNvbT6JAc4EEwEIADgWIQTz
+s5F4N5kLIvWqOZMfGViwZLMHwUCXaB6fwIbAwULCQgHAgYVCgkICwIEFgIDAQIe
AQIXgAAKCRBMfGViwZLMH/NoC/0UAvpTvT1sBD6qFpUOPZUmUSLLndtLzuYoMqID
0vvTdxb1PdbQpVX2sMuS19upyAmkVRh50uxGcsOLU/lUaF8C1C22zeGvtdkbw+79
Gv1AYlyCCEanhQSdH4z/t8W8nBcSw8kA+423guSzlIrrRSPCIyHSTP/MlwituN1+
wEUlXMXnjY4nNpyik+e9LoKK05zCy1mYswAnx1I5IH44iOfjqjz2FGv3iFhuc5rt
cEC26RyYCNVH7mIcCwd25/Np+IQbftfUVEugr1OGsSvdbAA3qWRtC9Q7VcFXy3A8
1svxkGPiZw60oxkG9V5v1l/ETCWztzvZvXXXZcWNaDb81rpn2LFeFulJKBxLLonC
gR/8l1hJAt8uS8ymOQRpVK9QVztlyxtZWZ7FxsfsC4AXthU3VFZjLUd1Tf4k5eW2
ov9JPjcSHHBQp6ScjtSgLTb4s2B5mD7VFBhuFOTWs1mbpVaRVIpguvYKIdxtDfek
0bjPQSI62K9G8mKGE4SqibfXhhO5AY0EXaB6fwEMALrPejAgOh7IWxmWJPO++8Fv
8eJD5nU7I3I4cWgJolXDSP4gEpkwlfHzAn7BJwTKTvZ5oDqpQCQV3mwqumQlRBKS
DHXU3b1Z4MOq3SbQlFfNduTCzKa7a79/DFf96TXilpVW/XT3HdN69810oCfo87Ub
/fx2G6h9JLaxdwJ57b/8Ej4eNbclGgE4GYHP9Xf0FX7F2xIqaIm/RCTGf7uGlaU0
RmeEFmy69T7jUAGI7g1gN1eldQ0F1q2HPuhP4iP39ZAz9K4Oyzl+B2IcHXyH2MjP
WXfgjVi87O5rEUvA/cpYU5WFc8hflP7cil16rb/PiALzEx+GCpdARxvtMT/IbK/3
luC2l/uw2ZYwtaL+8e9vyDOkVaWTD408Q51qrIANWwwLUSn71TuImGxCDzeuN79V
/T5PSjR5o/s6lR0CGzNL/B3MziuD2Vr5Wl1LYkJfTlgmGrnm6aJ/zKbrOMnkeAu5
Q0VgVOyibKhTu31WdXJ/jbhPQ5yd4UkduSAODStsRQARAQABiQG2BBgBCAAgFiEE
8/rOReDeZCyL1qjmTHxlYsGSzB8FAl2gen8CGwwACgkQTHxlYsGSzB8v7Av+IC9I
t7U3W51hCXH2wNcaSi8hxSYpFMl7GMX9zSKE8nKDmKBXUV7RJtU3cpGiGvgl+LLw
qBtjahRP+PU8AQSLL/4W97ldQrrdnOET6mtEiJylliA187SkimSixyy31YnUKDn6
PIeapJaoJ+JI22VhqbGd5tJCDbjTRFyiJP0L6vCEUAoLhpaqsqUiUw86//USl3uh
P+G9m2z3QPmxVFP/xZFEbihprpe/AccDLFjTwEWAMag6vV0NoI0E+JGeICKtzkxB
Pgi71N/jHKULPVMPXkaD30GT4k72lmuwqfvLz9uEhgSeAHakma8wUlp0aSw+kk4a
dZmqpBXcl6VFSDpCJXANUS4IUqjVqnK4nAGONR4JFaoejtAnmlz61EtjWuzPYjQS
0dL1Jv69WXLal7tzTJOZLekHas8DxzMgkID4IXCaSjwDb34mVgdaWyD1E302U3eX
IfS6J8Zp6Bs1baubHXFifXU6SV805b6i46/1m99OPsVH85zCUHvu4asaiLcR
=qIjw
-----END PGP PUBLIC KEY BLOCK-----

Двоичные данные
tests/development-public-key.gpg Обычный файл

Двоичный файл не отображается.

42
tests/test-public-key.plugin.gpg Обычный файл
Просмотреть файл

@@ -0,0 +1,42 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
23weBF1kMz0BDACoQ4hnmfz/KPQOx/J7fWj8DO/3HxAG+Y4rjvmGkNsaK02aNL7r
AQMAX0X5aRGB880Gm/n3lZSOp2wyjb5N4MnzOI/M3lww+rnJzt0i21GwOqS/HDz0
ZlAcO+nYecD5rkhHRUFeOnBzGKmV267VTwCTOjUcjdsuy2jLIZBk7xBV41n9gKZo
rwzYnOFLw5suEN8sCPYxgFe5iuRj8wEHzW0JiMWUb8PMm1eWkR3ux0G9wRpQCAdh
rB+RlKjG0UdfC3RhPR3fC0vqQkFj0tjM2G1xL3pLZIgygtZAsDAjPbeNzBBagzEv
WzqJiK8qHoKGxujyXfQIRWDrnfdbgwsscKZHOpse5PMDRTIGroq0DG/5JaBaEkEb
FwcvOe7CCOdPShZGMSFFncoblts4P+NgmwZHJH/sb3cbgivnSQT/SdnlYRI2vD9W
CI345hjk/2ynF68uat+NxPtWPjPk/MOnHz170iPOF5Zp0glKV6RUV+Lj2U8HdEVM
huHvQao59Shuas0AEQEAAbRSSmVzc2UgSGFsbGFtIChQbHVnaW4gTWFya2V0cGxh
Y2UgU2lnbmluZyBLZXkgLSBEZXZlbG9wbWVudCkgPGplc3NlQG1hdHRlcm1vc3Qu
Y29tPokB1AQTAQgAPhYhBLZ9dC4GijhaT2pBUp7t0EEt1PgpBQJdZDM9AhsDBQkA
J40ABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJ7t0EEt1Pgp3ikMAId6Syf8
qb5QyU4/KS5myEbz6buP423Rgs9My6R7IkwPheQDf4ZF5W2rIlrV2bVf3VFvTfuT
PXDInfBxr6sQtItP65+ct34C9GtG7xost+RzM5ArjsY1jRH276tJMQzfNeXXvtY3
Fcahz5Wa1DBUAgUbArV9yzMprxMgNFLsVCYCl3/1iO3aXf1nw2GVJ+0nJ6YhGv3E
Q1WHo0bkv2w64EzsnsBoKZCCuE/MK4k3MxDDWiva+OfuWnooV6QTBRm6C8wOIKXs
tnAE8nvciKobD7VcZW09F+p/Jg1NbroyCaGDROiLtwUenEpyUx4zeYvAvFGTQz5c
5zYGoZENzdG7UH7PiT7QlLqQx4rWpJH08D8ciDKeRyR+wiocP0/hh7UT+Wg0qnTV
F7b6e5YORjLOjZKtp8nfFB9/Ql0L+7wzQzGz7NSojGWR1XdpGDpZTFts1WR6r4dc
qjww52FBwjFdBNsaWJAsdJGzD2YT9GvzJZMUcSTlus8kAySKM+UVV6ZZSbkBjQRd
ZDM9AQwAosl4FEbSDetXUf3dUJMqQipQAyhN7dJd3Gv4gfgW16ymzcV0jh/F6LJP
JN+iRYziBvDmLDuBXaasdJvtemp2xljvI2x9+JFSFf8ZCqvKerwYNWb1icdzjzof
Ki32PJxn8o/iJGV5xkhqw+PNOEE12RBgOLwLVAQP3FxS8mgzCM+cJKmIWYeVSoy2
J3fDyZWyzQ5zBsvK/HtSW1R3MfhoRJq7E1VFVzrzmQvTSG3J+5LpwT0snMX7qona
Rm7OovP3PsvlY2BB7+CLj7N6G3BQRTpfJndfea+4DfQlVYw0mEUlT77DXitfX701
veVZu9cRg8PLaoHSRbU2BreDZRVPMq93svLxT7MCCBrau6wVOZQ5kqObDImSbxP6
mNB+hi2HurNcB+LhwgVboC75q0CzdnaPqu2c2YlsvsaZWkK2jlvNyqlN9uoJ1bUp
56hBWUOYAHx3hsZdEb4In95sP7cZAtKQJU0Q64UR/OPlU/a5fK591RLVlNY6LESX
aZLsFMMbABEBAAGJAbwEGAEIACYWIQS2fXQuBoo4Wk9qQVKe7dBBLdT4KQUCXWQz
PQIbDAUJACeNAAAKCRCe7dBBLdT4KelxC/9ze8cmYoLK2tr2cArX3Xw41wiXi4hV
dMMSy8Y8ZUCFNBDIf1do3j6XMQ8EJROF2IBMnQFej7K4IJAQ/0rWFuE3rBS6g+n1
7wiFMfj24RTHy/i303DWuveV7vbJw2oBYVY892hQAKxWpN/On4bctkHLxAYUpDu0
EmtWkbsjet01tvZKRA6ONQcd/bzrkTVmJS4a5u3nIkaj2xx5YQGLrCY+ovDRc3Vg
YSsIjJj5oOX52JabkE+XTi+g4wlAwIlMH1AB4qTS0HkHyLnLaNyjlNMwdiVQbiEC
IwZrV+YU1byphlmbB8N08aIAXTNRkyqYgDiDY0aicPRxex3BRXBjYpD01hYkhnsZ
HxZekApznHlf3C4a5SztKe1uR/A1GHRYMYuXUKc859SMIf69E/Vhr0T5m46R501h
SQgCdRZmsX5Qo3JmjEFyVM1KfjxdsBt8O1uZOEgBuHEMMFRrsDhBPr6B/cPwoT5G
gdlxUIdsQJ/ReVe1OOrgASKbG3qApwert2g=
=FQgT
-----END PGP PUBLIC KEY BLOCK-----

14
tests/testplugin.tar.gz.asc Обычный файл
Просмотреть файл

@@ -0,0 +1,14 @@
-----BEGIN PGP SIGNATURE-----
iQGzBAABCAAdFiEE8/rOReDeZCyL1qjmTHxlYsGSzB8FAl26+QIACgkQTHxlYsGS
zB9SFwwAmexJelfpTRjnABJKWnFKuGOTNosVNOWqE+k//n1PV3H3uCzsluh/GWIb
p5Nk6+UJieNvyAaQhIT5+3Rv0HkYvz4N15813SvQ8KOL8vE7oRFHttsZHpDKTpk1
fQrd9JISMdViVj4x8VltgYkUIJDmULPfF9/m1OGV/BsWuHmmaEp3yw+KLdhL1j3U
LlfW453nHvXrGT2rO3l+KDIVMt4zG6LVmL1C29yWtBplKxjgXKg/EbPcI31/8D9X
QoIz9fUIFzqt4L1IfJhL6NlUiDc1iSKf4STiU2CzmHdKqRD9FdwgzZlG5P/xxCgu
BVNy/c9musZbK9LtapncC/S62uwZzH5naTrCDs4F1VE2P5gjU9iv3AXVaeT4hLuI
EbluXzmAt9hjox2s0O0X6EgW68FbUVX9bvu1tY9sUvWlXNCL2hrybF3YS3cZ2wGx
rmdWmwktXDpJ8iN/b1+quw0CVC7myAefPK0U8356tktZHwcQEp2fmrF4PqX2qfJf
cDzhj2yg
=J9kV
-----END PGP SIGNATURE-----

Двоичные данные
tests/testplugin.tar.gz.sig Обычный файл

Двоичный файл не отображается.

Двоичные данные
tests/testpluginv2.tar.gz

Двоичный файл не отображается.

Двоичные данные
tests/testpluginv2.tar.gz.sig Обычный файл

Двоичный файл не отображается.

Просмотреть файл

@@ -19,6 +19,15 @@ func StringInSlice(a string, slice []string) bool {
return false return false
} }
func RemoveStringFromSlice(a string, slice []string) []string {
for i, str := range slice {
if str == a {
return append(slice[:i], slice[i+1:]...)
}
}
return slice
}
func StringArrayIntersection(arr1, arr2 []string) []string { func StringArrayIntersection(arr1, arr2 []string) []string {
arrMap := map[string]bool{} arrMap := map[string]bool{}
result := []string{} result := []string{}

Просмотреть файл

@@ -153,3 +153,10 @@ func TestGetIpAddress(t *testing.T) {
assert.Equal(t, "10.1.0.1", GetIpAddress(&httpRequest10, []string{"X-Real-Ip", "X-Forwarded-For"})) assert.Equal(t, "10.1.0.1", GetIpAddress(&httpRequest10, []string{"X-Real-Ip", "X-Forwarded-For"}))
} }
func TestRemoveStringFromSlice(t *testing.T) {
a := []string{"one", "two", "three", "four", "five", "six"}
expected := []string{"one", "two", "three", "five", "six"}
assert.Equal(t, RemoveStringFromSlice("four", a), expected)
}

533
vendor/golang.org/x/crypto/cast5/cast5.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,533 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cast5 implements CAST5, as defined in RFC 2144.
//
// CAST5 is a legacy cipher and its short block size makes it vulnerable to
// birthday bound attacks (see https://sweet32.info). It should only be used
// where compatibility with legacy systems, not security, is the goal.
//
// Deprecated: any new system should use AES (from crypto/aes, if necessary in
// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from
// golang.org/x/crypto/chacha20poly1305).
package cast5 // import "golang.org/x/crypto/cast5"
import "errors"
const BlockSize = 8
const KeySize = 16
type Cipher struct {
masking [16]uint32
rotate [16]uint8
}
func NewCipher(key []byte) (c *Cipher, err error) {
if len(key) != KeySize {
return nil, errors.New("CAST5: keys must be 16 bytes")
}
c = new(Cipher)
c.keySchedule(key)
return
}
func (c *Cipher) BlockSize() int {
return BlockSize
}
func (c *Cipher) Encrypt(dst, src []byte) {
l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
l, r = r, l^f1(r, c.masking[0], c.rotate[0])
l, r = r, l^f2(r, c.masking[1], c.rotate[1])
l, r = r, l^f3(r, c.masking[2], c.rotate[2])
l, r = r, l^f1(r, c.masking[3], c.rotate[3])
l, r = r, l^f2(r, c.masking[4], c.rotate[4])
l, r = r, l^f3(r, c.masking[5], c.rotate[5])
l, r = r, l^f1(r, c.masking[6], c.rotate[6])
l, r = r, l^f2(r, c.masking[7], c.rotate[7])
l, r = r, l^f3(r, c.masking[8], c.rotate[8])
l, r = r, l^f1(r, c.masking[9], c.rotate[9])
l, r = r, l^f2(r, c.masking[10], c.rotate[10])
l, r = r, l^f3(r, c.masking[11], c.rotate[11])
l, r = r, l^f1(r, c.masking[12], c.rotate[12])
l, r = r, l^f2(r, c.masking[13], c.rotate[13])
l, r = r, l^f3(r, c.masking[14], c.rotate[14])
l, r = r, l^f1(r, c.masking[15], c.rotate[15])
dst[0] = uint8(r >> 24)
dst[1] = uint8(r >> 16)
dst[2] = uint8(r >> 8)
dst[3] = uint8(r)
dst[4] = uint8(l >> 24)
dst[5] = uint8(l >> 16)
dst[6] = uint8(l >> 8)
dst[7] = uint8(l)
}
func (c *Cipher) Decrypt(dst, src []byte) {
l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
l, r = r, l^f1(r, c.masking[15], c.rotate[15])
l, r = r, l^f3(r, c.masking[14], c.rotate[14])
l, r = r, l^f2(r, c.masking[13], c.rotate[13])
l, r = r, l^f1(r, c.masking[12], c.rotate[12])
l, r = r, l^f3(r, c.masking[11], c.rotate[11])
l, r = r, l^f2(r, c.masking[10], c.rotate[10])
l, r = r, l^f1(r, c.masking[9], c.rotate[9])
l, r = r, l^f3(r, c.masking[8], c.rotate[8])
l, r = r, l^f2(r, c.masking[7], c.rotate[7])
l, r = r, l^f1(r, c.masking[6], c.rotate[6])
l, r = r, l^f3(r, c.masking[5], c.rotate[5])
l, r = r, l^f2(r, c.masking[4], c.rotate[4])
l, r = r, l^f1(r, c.masking[3], c.rotate[3])
l, r = r, l^f3(r, c.masking[2], c.rotate[2])
l, r = r, l^f2(r, c.masking[1], c.rotate[1])
l, r = r, l^f1(r, c.masking[0], c.rotate[0])
dst[0] = uint8(r >> 24)
dst[1] = uint8(r >> 16)
dst[2] = uint8(r >> 8)
dst[3] = uint8(r)
dst[4] = uint8(l >> 24)
dst[5] = uint8(l >> 16)
dst[6] = uint8(l >> 8)
dst[7] = uint8(l)
}
type keyScheduleA [4][7]uint8
type keyScheduleB [4][5]uint8
// keyScheduleRound contains the magic values for a round of the key schedule.
// The keyScheduleA deals with the lines like:
// z0z1z2z3 = x0x1x2x3 ^ S5[xD] ^ S6[xF] ^ S7[xC] ^ S8[xE] ^ S7[x8]
// Conceptually, both x and z are in the same array, x first. The first
// element describes which word of this array gets written to and the
// second, which word gets read. So, for the line above, it's "4, 0", because
// it's writing to the first word of z, which, being after x, is word 4, and
// reading from the first word of x: word 0.
//
// Next are the indexes into the S-boxes. Now the array is treated as bytes. So
// "xD" is 0xd. The first byte of z is written as "16 + 0", just to be clear
// that it's z that we're indexing.
//
// keyScheduleB deals with lines like:
// K1 = S5[z8] ^ S6[z9] ^ S7[z7] ^ S8[z6] ^ S5[z2]
// "K1" is ignored because key words are always written in order. So the five
// elements are the S-box indexes. They use the same form as in keyScheduleA,
// above.
type keyScheduleRound struct{}
type keySchedule []keyScheduleRound
var schedule = []struct {
a keyScheduleA
b keyScheduleB
}{
{
keyScheduleA{
{4, 0, 0xd, 0xf, 0xc, 0xe, 0x8},
{5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
{6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
{7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
},
keyScheduleB{
{16 + 8, 16 + 9, 16 + 7, 16 + 6, 16 + 2},
{16 + 0xa, 16 + 0xb, 16 + 5, 16 + 4, 16 + 6},
{16 + 0xc, 16 + 0xd, 16 + 3, 16 + 2, 16 + 9},
{16 + 0xe, 16 + 0xf, 16 + 1, 16 + 0, 16 + 0xc},
},
},
{
keyScheduleA{
{0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
{1, 4, 0, 2, 1, 3, 16 + 2},
{2, 5, 7, 6, 5, 4, 16 + 1},
{3, 7, 0xa, 9, 0xb, 8, 16 + 3},
},
keyScheduleB{
{3, 2, 0xc, 0xd, 8},
{1, 0, 0xe, 0xf, 0xd},
{7, 6, 8, 9, 3},
{5, 4, 0xa, 0xb, 7},
},
},
{
keyScheduleA{
{4, 0, 0xd, 0xf, 0xc, 0xe, 8},
{5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
{6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
{7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
},
keyScheduleB{
{16 + 3, 16 + 2, 16 + 0xc, 16 + 0xd, 16 + 9},
{16 + 1, 16 + 0, 16 + 0xe, 16 + 0xf, 16 + 0xc},
{16 + 7, 16 + 6, 16 + 8, 16 + 9, 16 + 2},
{16 + 5, 16 + 4, 16 + 0xa, 16 + 0xb, 16 + 6},
},
},
{
keyScheduleA{
{0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
{1, 4, 0, 2, 1, 3, 16 + 2},
{2, 5, 7, 6, 5, 4, 16 + 1},
{3, 7, 0xa, 9, 0xb, 8, 16 + 3},
},
keyScheduleB{
{8, 9, 7, 6, 3},
{0xa, 0xb, 5, 4, 7},
{0xc, 0xd, 3, 2, 8},
{0xe, 0xf, 1, 0, 0xd},
},
},
}
func (c *Cipher) keySchedule(in []byte) {
var t [8]uint32
var k [32]uint32
for i := 0; i < 4; i++ {
j := i * 4
t[i] = uint32(in[j])<<24 | uint32(in[j+1])<<16 | uint32(in[j+2])<<8 | uint32(in[j+3])
}
x := []byte{6, 7, 4, 5}
ki := 0
for half := 0; half < 2; half++ {
for _, round := range schedule {
for j := 0; j < 4; j++ {
var a [7]uint8
copy(a[:], round.a[j][:])
w := t[a[1]]
w ^= sBox[4][(t[a[2]>>2]>>(24-8*(a[2]&3)))&0xff]
w ^= sBox[5][(t[a[3]>>2]>>(24-8*(a[3]&3)))&0xff]
w ^= sBox[6][(t[a[4]>>2]>>(24-8*(a[4]&3)))&0xff]
w ^= sBox[7][(t[a[5]>>2]>>(24-8*(a[5]&3)))&0xff]
w ^= sBox[x[j]][(t[a[6]>>2]>>(24-8*(a[6]&3)))&0xff]
t[a[0]] = w
}
for j := 0; j < 4; j++ {
var b [5]uint8
copy(b[:], round.b[j][:])
w := sBox[4][(t[b[0]>>2]>>(24-8*(b[0]&3)))&0xff]
w ^= sBox[5][(t[b[1]>>2]>>(24-8*(b[1]&3)))&0xff]
w ^= sBox[6][(t[b[2]>>2]>>(24-8*(b[2]&3)))&0xff]
w ^= sBox[7][(t[b[3]>>2]>>(24-8*(b[3]&3)))&0xff]
w ^= sBox[4+j][(t[b[4]>>2]>>(24-8*(b[4]&3)))&0xff]
k[ki] = w
ki++
}
}
}
for i := 0; i < 16; i++ {
c.masking[i] = k[i]
c.rotate[i] = uint8(k[16+i] & 0x1f)
}
}
// These are the three 'f' functions. See RFC 2144, section 2.2.
func f1(d, m uint32, r uint8) uint32 {
t := m + d
I := (t << r) | (t >> (32 - r))
return ((sBox[0][I>>24] ^ sBox[1][(I>>16)&0xff]) - sBox[2][(I>>8)&0xff]) + sBox[3][I&0xff]
}
func f2(d, m uint32, r uint8) uint32 {
t := m ^ d
I := (t << r) | (t >> (32 - r))
return ((sBox[0][I>>24] - sBox[1][(I>>16)&0xff]) + sBox[2][(I>>8)&0xff]) ^ sBox[3][I&0xff]
}
func f3(d, m uint32, r uint8) uint32 {
t := m - d
I := (t << r) | (t >> (32 - r))
return ((sBox[0][I>>24] + sBox[1][(I>>16)&0xff]) ^ sBox[2][(I>>8)&0xff]) - sBox[3][I&0xff]
}
var sBox = [8][256]uint32{
{
0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949,
0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e,
0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d,
0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0,
0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7,
0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935,
0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d,
0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50,
0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe,
0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3,
0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167,
0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291,
0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779,
0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2,
0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511,
0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d,
0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5,
0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324,
0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c,
0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc,
0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d,
0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96,
0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a,
0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d,
0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd,
0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6,
0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9,
0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872,
0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c,
0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e,
0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9,
0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf,
},
{
0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651,
0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3,
0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb,
0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806,
0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b,
0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359,
0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b,
0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c,
0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34,
0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb,
0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd,
0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860,
0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b,
0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304,
0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b,
0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf,
0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c,
0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13,
0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f,
0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6,
0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6,
0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58,
0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906,
0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d,
0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6,
0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4,
0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6,
0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f,
0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249,
0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa,
0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9,
0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1,
},
{
0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90,
0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5,
0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e,
0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240,
0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5,
0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b,
0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71,
0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04,
0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82,
0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15,
0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2,
0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176,
0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148,
0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc,
0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341,
0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e,
0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51,
0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f,
0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a,
0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b,
0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b,
0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5,
0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45,
0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536,
0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc,
0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0,
0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69,
0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2,
0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49,
0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d,
0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a,
0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783,
},
{
0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1,
0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf,
0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15,
0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121,
0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25,
0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5,
0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb,
0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5,
0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d,
0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6,
0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23,
0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003,
0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6,
0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119,
0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24,
0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a,
0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79,
0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df,
0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26,
0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab,
0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7,
0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417,
0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2,
0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2,
0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a,
0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919,
0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef,
0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876,
0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab,
0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04,
0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282,
0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2,
},
{
0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f,
0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a,
0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff,
0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02,
0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a,
0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7,
0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9,
0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981,
0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774,
0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655,
0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2,
0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910,
0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1,
0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da,
0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049,
0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f,
0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba,
0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be,
0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3,
0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840,
0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4,
0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2,
0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7,
0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5,
0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e,
0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e,
0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801,
0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad,
0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0,
0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20,
0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8,
0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4,
},
{
0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac,
0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138,
0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367,
0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98,
0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072,
0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3,
0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd,
0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8,
0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9,
0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54,
0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387,
0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc,
0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf,
0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf,
0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f,
0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289,
0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950,
0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f,
0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b,
0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be,
0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13,
0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976,
0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0,
0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891,
0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da,
0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc,
0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084,
0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25,
0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121,
0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5,
0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd,
0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f,
},
{
0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f,
0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de,
0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43,
0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19,
0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2,
0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516,
0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88,
0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816,
0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756,
0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a,
0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264,
0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688,
0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28,
0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3,
0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7,
0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06,
0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033,
0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a,
0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566,
0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509,
0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962,
0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e,
0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c,
0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c,
0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285,
0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301,
0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be,
0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767,
0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647,
0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914,
0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c,
0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3,
},
{
0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5,
0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc,
0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd,
0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d,
0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2,
0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862,
0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc,
0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c,
0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e,
0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039,
0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8,
0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42,
0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5,
0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472,
0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225,
0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c,
0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb,
0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054,
0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70,
0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc,
0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c,
0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3,
0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4,
0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101,
0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f,
0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e,
0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a,
0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c,
0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384,
0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c,
0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82,
0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e,
},
}

219
vendor/golang.org/x/crypto/openpgp/armor/armor.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,219 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
// very similar to PEM except that it has an additional CRC checksum.
package armor // import "golang.org/x/crypto/openpgp/armor"
import (
"bufio"
"bytes"
"encoding/base64"
"golang.org/x/crypto/openpgp/errors"
"io"
)
// A Block represents an OpenPGP armored structure.
//
// The encoded form is:
// -----BEGIN Type-----
// Headers
//
// base64-encoded Bytes
// '=' base64 encoded checksum
// -----END Type-----
// where Headers is a possibly empty sequence of Key: Value lines.
//
// Since the armored data can be very large, this package presents a streaming
// interface.
type Block struct {
Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE").
Header map[string]string // Optional headers.
Body io.Reader // A Reader from which the contents can be read
lReader lineReader
oReader openpgpReader
}
var ArmorCorrupt error = errors.StructuralError("armor invalid")
const crc24Init = 0xb704ce
const crc24Poly = 0x1864cfb
const crc24Mask = 0xffffff
// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
func crc24(crc uint32, d []byte) uint32 {
for _, b := range d {
crc ^= uint32(b) << 16
for i := 0; i < 8; i++ {
crc <<= 1
if crc&0x1000000 != 0 {
crc ^= crc24Poly
}
}
}
return crc
}
var armorStart = []byte("-----BEGIN ")
var armorEnd = []byte("-----END ")
var armorEndOfLine = []byte("-----")
// lineReader wraps a line based reader. It watches for the end of an armor
// block and records the expected CRC value.
type lineReader struct {
in *bufio.Reader
buf []byte
eof bool
crc uint32
}
func (l *lineReader) Read(p []byte) (n int, err error) {
if l.eof {
return 0, io.EOF
}
if len(l.buf) > 0 {
n = copy(p, l.buf)
l.buf = l.buf[n:]
return
}
line, isPrefix, err := l.in.ReadLine()
if err != nil {
return
}
if isPrefix {
return 0, ArmorCorrupt
}
if len(line) == 5 && line[0] == '=' {
// This is the checksum line
var expectedBytes [3]byte
var m int
m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:])
if m != 3 || err != nil {
return
}
l.crc = uint32(expectedBytes[0])<<16 |
uint32(expectedBytes[1])<<8 |
uint32(expectedBytes[2])
line, _, err = l.in.ReadLine()
if err != nil && err != io.EOF {
return
}
if !bytes.HasPrefix(line, armorEnd) {
return 0, ArmorCorrupt
}
l.eof = true
return 0, io.EOF
}
if len(line) > 96 {
return 0, ArmorCorrupt
}
n = copy(p, line)
bytesToSave := len(line) - n
if bytesToSave > 0 {
if cap(l.buf) < bytesToSave {
l.buf = make([]byte, 0, bytesToSave)
}
l.buf = l.buf[0:bytesToSave]
copy(l.buf, line[n:])
}
return
}
// openpgpReader passes Read calls to the underlying base64 decoder, but keeps
// a running CRC of the resulting data and checks the CRC against the value
// found by the lineReader at EOF.
type openpgpReader struct {
lReader *lineReader
b64Reader io.Reader
currentCRC uint32
}
func (r *openpgpReader) Read(p []byte) (n int, err error) {
n, err = r.b64Reader.Read(p)
r.currentCRC = crc24(r.currentCRC, p[:n])
if err == io.EOF {
if r.lReader.crc != uint32(r.currentCRC&crc24Mask) {
return 0, ArmorCorrupt
}
}
return
}
// Decode reads a PGP armored block from the given Reader. It will ignore
// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The
// given Reader is not usable after calling this function: an arbitrary amount
// of data may have been read past the end of the block.
func Decode(in io.Reader) (p *Block, err error) {
r := bufio.NewReaderSize(in, 100)
var line []byte
ignoreNext := false
TryNextBlock:
p = nil
// Skip leading garbage
for {
ignoreThis := ignoreNext
line, ignoreNext, err = r.ReadLine()
if err != nil {
return
}
if ignoreNext || ignoreThis {
continue
}
line = bytes.TrimSpace(line)
if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
break
}
}
p = new(Block)
p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
p.Header = make(map[string]string)
nextIsContinuation := false
var lastKey string
// Read headers
for {
isContinuation := nextIsContinuation
line, nextIsContinuation, err = r.ReadLine()
if err != nil {
p = nil
return
}
if isContinuation {
p.Header[lastKey] += string(line)
continue
}
line = bytes.TrimSpace(line)
if len(line) == 0 {
break
}
i := bytes.Index(line, []byte(": "))
if i == -1 {
goto TryNextBlock
}
lastKey = string(line[:i])
p.Header[lastKey] = string(line[i+2:])
}
p.lReader.in = r
p.oReader.currentCRC = crc24Init
p.oReader.lReader = &p.lReader
p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
p.Body = &p.oReader
return
}

160
vendor/golang.org/x/crypto/openpgp/armor/encode.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,160 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package armor
import (
"encoding/base64"
"io"
)
var armorHeaderSep = []byte(": ")
var blockEnd = []byte("\n=")
var newline = []byte("\n")
var armorEndOfLineOut = []byte("-----\n")
// writeSlices writes its arguments to the given Writer.
func writeSlices(out io.Writer, slices ...[]byte) (err error) {
for _, s := range slices {
_, err = out.Write(s)
if err != nil {
return err
}
}
return
}
// lineBreaker breaks data across several lines, all of the same byte length
// (except possibly the last). Lines are broken with a single '\n'.
type lineBreaker struct {
lineLength int
line []byte
used int
out io.Writer
haveWritten bool
}
func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
return &lineBreaker{
lineLength: lineLength,
line: make([]byte, lineLength),
used: 0,
out: out,
}
}
func (l *lineBreaker) Write(b []byte) (n int, err error) {
n = len(b)
if n == 0 {
return
}
if l.used == 0 && l.haveWritten {
_, err = l.out.Write([]byte{'\n'})
if err != nil {
return
}
}
if l.used+len(b) < l.lineLength {
l.used += copy(l.line[l.used:], b)
return
}
l.haveWritten = true
_, err = l.out.Write(l.line[0:l.used])
if err != nil {
return
}
excess := l.lineLength - l.used
l.used = 0
_, err = l.out.Write(b[0:excess])
if err != nil {
return
}
_, err = l.Write(b[excess:])
return
}
func (l *lineBreaker) Close() (err error) {
if l.used > 0 {
_, err = l.out.Write(l.line[0:l.used])
if err != nil {
return
}
}
return
}
// encoding keeps track of a running CRC24 over the data which has been written
// to it and outputs a OpenPGP checksum when closed, followed by an armor
// trailer.
//
// It's built into a stack of io.Writers:
// encoding -> base64 encoder -> lineBreaker -> out
type encoding struct {
out io.Writer
breaker *lineBreaker
b64 io.WriteCloser
crc uint32
blockType []byte
}
func (e *encoding) Write(data []byte) (n int, err error) {
e.crc = crc24(e.crc, data)
return e.b64.Write(data)
}
func (e *encoding) Close() (err error) {
err = e.b64.Close()
if err != nil {
return
}
e.breaker.Close()
var checksumBytes [3]byte
checksumBytes[0] = byte(e.crc >> 16)
checksumBytes[1] = byte(e.crc >> 8)
checksumBytes[2] = byte(e.crc)
var b64ChecksumBytes [4]byte
base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine)
}
// Encode returns a WriteCloser which will encode the data written to it in
// OpenPGP armor.
func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
bType := []byte(blockType)
err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
if err != nil {
return
}
for k, v := range headers {
err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
if err != nil {
return
}
}
_, err = out.Write(newline)
if err != nil {
return
}
e := &encoding{
out: out,
breaker: newLineBreaker(out, 64),
crc: crc24Init,
blockType: bType,
}
e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
return e, nil
}

59
vendor/golang.org/x/crypto/openpgp/canonical_text.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,59 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package openpgp
import "hash"
// NewCanonicalTextHash reformats text written to it into the canonical
// form and then applies the hash h. See RFC 4880, section 5.2.1.
func NewCanonicalTextHash(h hash.Hash) hash.Hash {
return &canonicalTextHash{h, 0}
}
type canonicalTextHash struct {
h hash.Hash
s int
}
var newline = []byte{'\r', '\n'}
func (cth *canonicalTextHash) Write(buf []byte) (int, error) {
start := 0
for i, c := range buf {
switch cth.s {
case 0:
if c == '\r' {
cth.s = 1
} else if c == '\n' {
cth.h.Write(buf[start:i])
cth.h.Write(newline)
start = i + 1
}
case 1:
cth.s = 0
}
}
cth.h.Write(buf[start:])
return len(buf), nil
}
func (cth *canonicalTextHash) Sum(in []byte) []byte {
return cth.h.Sum(in)
}
func (cth *canonicalTextHash) Reset() {
cth.h.Reset()
cth.s = 0
}
func (cth *canonicalTextHash) Size() int {
return cth.h.Size()
}
func (cth *canonicalTextHash) BlockSize() int {
return cth.h.BlockSize()
}

122
vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,122 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package elgamal implements ElGamal encryption, suitable for OpenPGP,
// as specified in "A Public-Key Cryptosystem and a Signature Scheme Based on
// Discrete Logarithms," IEEE Transactions on Information Theory, v. IT-31,
// n. 4, 1985, pp. 469-472.
//
// This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it
// unsuitable for other protocols. RSA should be used in preference in any
// case.
package elgamal // import "golang.org/x/crypto/openpgp/elgamal"
import (
"crypto/rand"
"crypto/subtle"
"errors"
"io"
"math/big"
)
// PublicKey represents an ElGamal public key.
type PublicKey struct {
G, P, Y *big.Int
}
// PrivateKey represents an ElGamal private key.
type PrivateKey struct {
PublicKey
X *big.Int
}
// Encrypt encrypts the given message to the given public key. The result is a
// pair of integers. Errors can result from reading random, or because msg is
// too large to be encrypted to the public key.
func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) {
pLen := (pub.P.BitLen() + 7) / 8
if len(msg) > pLen-11 {
err = errors.New("elgamal: message too long")
return
}
// EM = 0x02 || PS || 0x00 || M
em := make([]byte, pLen-1)
em[0] = 2
ps, mm := em[1:len(em)-len(msg)-1], em[len(em)-len(msg):]
err = nonZeroRandomBytes(ps, random)
if err != nil {
return
}
em[len(em)-len(msg)-1] = 0
copy(mm, msg)
m := new(big.Int).SetBytes(em)
k, err := rand.Int(random, pub.P)
if err != nil {
return
}
c1 = new(big.Int).Exp(pub.G, k, pub.P)
s := new(big.Int).Exp(pub.Y, k, pub.P)
c2 = s.Mul(s, m)
c2.Mod(c2, pub.P)
return
}
// Decrypt takes two integers, resulting from an ElGamal encryption, and
// returns the plaintext of the message. An error can result only if the
// ciphertext is invalid. Users should keep in mind that this is a padding
// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can
// be used to break the cryptosystem. See ``Chosen Ciphertext Attacks
// Against Protocols Based on the RSA Encryption Standard PKCS #1'', Daniel
// Bleichenbacher, Advances in Cryptology (Crypto '98),
func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) {
s := new(big.Int).Exp(c1, priv.X, priv.P)
s.ModInverse(s, priv.P)
s.Mul(s, c2)
s.Mod(s, priv.P)
em := s.Bytes()
firstByteIsTwo := subtle.ConstantTimeByteEq(em[0], 2)
// The remainder of the plaintext must be a string of non-zero random
// octets, followed by a 0, followed by the message.
// lookingForIndex: 1 iff we are still looking for the zero.
// index: the offset of the first zero byte.
var lookingForIndex, index int
lookingForIndex = 1
for i := 1; i < len(em); i++ {
equals0 := subtle.ConstantTimeByteEq(em[i], 0)
index = subtle.ConstantTimeSelect(lookingForIndex&equals0, i, index)
lookingForIndex = subtle.ConstantTimeSelect(equals0, 0, lookingForIndex)
}
if firstByteIsTwo != 1 || lookingForIndex != 0 || index < 9 {
return nil, errors.New("elgamal: decryption error")
}
return em[index+1:], nil
}
// nonZeroRandomBytes fills the given slice with non-zero random octets.
func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) {
_, err = io.ReadFull(rand, s)
if err != nil {
return
}
for i := 0; i < len(s); i++ {
for s[i] == 0 {
_, err = io.ReadFull(rand, s[i:i+1])
if err != nil {
return
}
}
}
return
}

72
vendor/golang.org/x/crypto/openpgp/errors/errors.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,72 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errors contains common error types for the OpenPGP packages.
package errors // import "golang.org/x/crypto/openpgp/errors"
import (
"strconv"
)
// A StructuralError is returned when OpenPGP data is found to be syntactically
// invalid.
type StructuralError string
func (s StructuralError) Error() string {
return "openpgp: invalid data: " + string(s)
}
// UnsupportedError indicates that, although the OpenPGP data is valid, it
// makes use of currently unimplemented features.
type UnsupportedError string
func (s UnsupportedError) Error() string {
return "openpgp: unsupported feature: " + string(s)
}
// InvalidArgumentError indicates that the caller is in error and passed an
// incorrect value.
type InvalidArgumentError string
func (i InvalidArgumentError) Error() string {
return "openpgp: invalid argument: " + string(i)
}
// SignatureError indicates that a syntactically valid signature failed to
// validate.
type SignatureError string
func (b SignatureError) Error() string {
return "openpgp: invalid signature: " + string(b)
}
type keyIncorrectError int
func (ki keyIncorrectError) Error() string {
return "openpgp: incorrect key"
}
var ErrKeyIncorrect error = keyIncorrectError(0)
type unknownIssuerError int
func (unknownIssuerError) Error() string {
return "openpgp: signature made by unknown entity"
}
var ErrUnknownIssuer error = unknownIssuerError(0)
type keyRevokedError int
func (keyRevokedError) Error() string {
return "openpgp: signature made by revoked key"
}
var ErrKeyRevoked error = keyRevokedError(0)
type UnknownPacketTypeError uint8
func (upte UnknownPacketTypeError) Error() string {
return "openpgp: unknown packet type: " + strconv.Itoa(int(upte))
}

693
vendor/golang.org/x/crypto/openpgp/keys.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,693 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package openpgp
import (
"crypto/rsa"
"io"
"time"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
)
// PublicKeyType is the armor type for a PGP public key.
var PublicKeyType = "PGP PUBLIC KEY BLOCK"
// PrivateKeyType is the armor type for a PGP private key.
var PrivateKeyType = "PGP PRIVATE KEY BLOCK"
// An Entity represents the components of an OpenPGP key: a primary public key
// (which must be a signing key), one or more identities claimed by that key,
// and zero or more subkeys, which may be encryption keys.
type Entity struct {
PrimaryKey *packet.PublicKey
PrivateKey *packet.PrivateKey
Identities map[string]*Identity // indexed by Identity.Name
Revocations []*packet.Signature
Subkeys []Subkey
}
// An Identity represents an identity claimed by an Entity and zero or more
// assertions by other entities about that claim.
type Identity struct {
Name string // by convention, has the form "Full Name (comment) <email@example.com>"
UserId *packet.UserId
SelfSignature *packet.Signature
Signatures []*packet.Signature
}
// A Subkey is an additional public key in an Entity. Subkeys can be used for
// encryption.
type Subkey struct {
PublicKey *packet.PublicKey
PrivateKey *packet.PrivateKey
Sig *packet.Signature
}
// A Key identifies a specific public key in an Entity. This is either the
// Entity's primary key or a subkey.
type Key struct {
Entity *Entity
PublicKey *packet.PublicKey
PrivateKey *packet.PrivateKey
SelfSignature *packet.Signature
}
// A KeyRing provides access to public and private keys.
type KeyRing interface {
// KeysById returns the set of keys that have the given key id.
KeysById(id uint64) []Key
// KeysByIdAndUsage returns the set of keys with the given id
// that also meet the key usage given by requiredUsage.
// The requiredUsage is expressed as the bitwise-OR of
// packet.KeyFlag* values.
KeysByIdUsage(id uint64, requiredUsage byte) []Key
// DecryptionKeys returns all private keys that are valid for
// decryption.
DecryptionKeys() []Key
}
// primaryIdentity returns the Identity marked as primary or the first identity
// if none are so marked.
func (e *Entity) primaryIdentity() *Identity {
var firstIdentity *Identity
for _, ident := range e.Identities {
if firstIdentity == nil {
firstIdentity = ident
}
if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
return ident
}
}
return firstIdentity
}
// encryptionKey returns the best candidate Key for encrypting a message to the
// given Entity.
func (e *Entity) encryptionKey(now time.Time) (Key, bool) {
candidateSubkey := -1
// Iterate the keys to find the newest key
var maxTime time.Time
for i, subkey := range e.Subkeys {
if subkey.Sig.FlagsValid &&
subkey.Sig.FlagEncryptCommunications &&
subkey.PublicKey.PubKeyAlgo.CanEncrypt() &&
!subkey.Sig.KeyExpired(now) &&
(maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) {
candidateSubkey = i
maxTime = subkey.Sig.CreationTime
}
}
if candidateSubkey != -1 {
subkey := e.Subkeys[candidateSubkey]
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true
}
// If we don't have any candidate subkeys for encryption and
// the primary key doesn't have any usage metadata then we
// assume that the primary key is ok. Or, if the primary key is
// marked as ok to encrypt to, then we can obviously use it.
i := e.primaryIdentity()
if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications &&
e.PrimaryKey.PubKeyAlgo.CanEncrypt() &&
!i.SelfSignature.KeyExpired(now) {
return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true
}
// This Entity appears to be signing only.
return Key{}, false
}
// signingKey return the best candidate Key for signing a message with this
// Entity.
func (e *Entity) signingKey(now time.Time) (Key, bool) {
candidateSubkey := -1
for i, subkey := range e.Subkeys {
if subkey.Sig.FlagsValid &&
subkey.Sig.FlagSign &&
subkey.PublicKey.PubKeyAlgo.CanSign() &&
!subkey.Sig.KeyExpired(now) {
candidateSubkey = i
break
}
}
if candidateSubkey != -1 {
subkey := e.Subkeys[candidateSubkey]
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true
}
// If we have no candidate subkey then we assume that it's ok to sign
// with the primary key.
i := e.primaryIdentity()
if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagSign &&
!i.SelfSignature.KeyExpired(now) {
return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true
}
return Key{}, false
}
// An EntityList contains one or more Entities.
type EntityList []*Entity
// KeysById returns the set of keys that have the given key id.
func (el EntityList) KeysById(id uint64) (keys []Key) {
for _, e := range el {
if e.PrimaryKey.KeyId == id {
var selfSig *packet.Signature
for _, ident := range e.Identities {
if selfSig == nil {
selfSig = ident.SelfSignature
} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
selfSig = ident.SelfSignature
break
}
}
keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig})
}
for _, subKey := range e.Subkeys {
if subKey.PublicKey.KeyId == id {
keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
}
}
}
return
}
// KeysByIdAndUsage returns the set of keys with the given id that also meet
// the key usage given by requiredUsage. The requiredUsage is expressed as
// the bitwise-OR of packet.KeyFlag* values.
func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
for _, key := range el.KeysById(id) {
if len(key.Entity.Revocations) > 0 {
continue
}
if key.SelfSignature.RevocationReason != nil {
continue
}
if key.SelfSignature.FlagsValid && requiredUsage != 0 {
var usage byte
if key.SelfSignature.FlagCertify {
usage |= packet.KeyFlagCertify
}
if key.SelfSignature.FlagSign {
usage |= packet.KeyFlagSign
}
if key.SelfSignature.FlagEncryptCommunications {
usage |= packet.KeyFlagEncryptCommunications
}
if key.SelfSignature.FlagEncryptStorage {
usage |= packet.KeyFlagEncryptStorage
}
if usage&requiredUsage != requiredUsage {
continue
}
}
keys = append(keys, key)
}
return
}
// DecryptionKeys returns all private keys that are valid for decryption.
func (el EntityList) DecryptionKeys() (keys []Key) {
for _, e := range el {
for _, subKey := range e.Subkeys {
if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
}
}
}
return
}
// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file.
func ReadArmoredKeyRing(r io.Reader) (EntityList, error) {
block, err := armor.Decode(r)
if err == io.EOF {
return nil, errors.InvalidArgumentError("no armored data found")
}
if err != nil {
return nil, err
}
if block.Type != PublicKeyType && block.Type != PrivateKeyType {
return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type)
}
return ReadKeyRing(block.Body)
}
// ReadKeyRing reads one or more public/private keys. Unsupported keys are
// ignored as long as at least a single valid key is found.
func ReadKeyRing(r io.Reader) (el EntityList, err error) {
packets := packet.NewReader(r)
var lastUnsupportedError error
for {
var e *Entity
e, err = ReadEntity(packets)
if err != nil {
// TODO: warn about skipped unsupported/unreadable keys
if _, ok := err.(errors.UnsupportedError); ok {
lastUnsupportedError = err
err = readToNextPublicKey(packets)
} else if _, ok := err.(errors.StructuralError); ok {
// Skip unreadable, badly-formatted keys
lastUnsupportedError = err
err = readToNextPublicKey(packets)
}
if err == io.EOF {
err = nil
break
}
if err != nil {
el = nil
break
}
} else {
el = append(el, e)
}
}
if len(el) == 0 && err == nil {
err = lastUnsupportedError
}
return
}
// readToNextPublicKey reads packets until the start of the entity and leaves
// the first packet of the new entity in the Reader.
func readToNextPublicKey(packets *packet.Reader) (err error) {
var p packet.Packet
for {
p, err = packets.Next()
if err == io.EOF {
return
} else if err != nil {
if _, ok := err.(errors.UnsupportedError); ok {
err = nil
continue
}
return
}
if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey {
packets.Unread(p)
return
}
}
}
// ReadEntity reads an entity (public key, identities, subkeys etc) from the
// given Reader.
func ReadEntity(packets *packet.Reader) (*Entity, error) {
e := new(Entity)
e.Identities = make(map[string]*Identity)
p, err := packets.Next()
if err != nil {
return nil, err
}
var ok bool
if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok {
if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok {
packets.Unread(p)
return nil, errors.StructuralError("first packet was not a public/private key")
}
e.PrimaryKey = &e.PrivateKey.PublicKey
}
if !e.PrimaryKey.PubKeyAlgo.CanSign() {
return nil, errors.StructuralError("primary key cannot be used for signatures")
}
var revocations []*packet.Signature
EachPacket:
for {
p, err := packets.Next()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
switch pkt := p.(type) {
case *packet.UserId:
if err := addUserID(e, packets, pkt); err != nil {
return nil, err
}
case *packet.Signature:
if pkt.SigType == packet.SigTypeKeyRevocation {
revocations = append(revocations, pkt)
} else if pkt.SigType == packet.SigTypeDirectSignature {
// TODO: RFC4880 5.2.1 permits signatures
// directly on keys (eg. to bind additional
// revocation keys).
}
// Else, ignoring the signature as it does not follow anything
// we would know to attach it to.
case *packet.PrivateKey:
if pkt.IsSubkey == false {
packets.Unread(p)
break EachPacket
}
err = addSubkey(e, packets, &pkt.PublicKey, pkt)
if err != nil {
return nil, err
}
case *packet.PublicKey:
if pkt.IsSubkey == false {
packets.Unread(p)
break EachPacket
}
err = addSubkey(e, packets, pkt, nil)
if err != nil {
return nil, err
}
default:
// we ignore unknown packets
}
}
if len(e.Identities) == 0 {
return nil, errors.StructuralError("entity without any identities")
}
for _, revocation := range revocations {
err = e.PrimaryKey.VerifyRevocationSignature(revocation)
if err == nil {
e.Revocations = append(e.Revocations, revocation)
} else {
// TODO: RFC 4880 5.2.3.15 defines revocation keys.
return nil, errors.StructuralError("revocation signature signed by alternate key")
}
}
return e, nil
}
func addUserID(e *Entity, packets *packet.Reader, pkt *packet.UserId) error {
// Make a new Identity object, that we might wind up throwing away.
// We'll only add it if we get a valid self-signature over this
// userID.
identity := new(Identity)
identity.Name = pkt.Id
identity.UserId = pkt
for {
p, err := packets.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
sig, ok := p.(*packet.Signature)
if !ok {
packets.Unread(p)
break
}
if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId {
if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, e.PrimaryKey, sig); err != nil {
return errors.StructuralError("user ID self-signature invalid: " + err.Error())
}
identity.SelfSignature = sig
e.Identities[pkt.Id] = identity
} else {
identity.Signatures = append(identity.Signatures, sig)
}
}
return nil
}
func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error {
var subKey Subkey
subKey.PublicKey = pub
subKey.PrivateKey = priv
for {
p, err := packets.Next()
if err == io.EOF {
break
} else if err != nil {
return errors.StructuralError("subkey signature invalid: " + err.Error())
}
sig, ok := p.(*packet.Signature)
if !ok {
packets.Unread(p)
break
}
if sig.SigType != packet.SigTypeSubkeyBinding && sig.SigType != packet.SigTypeSubkeyRevocation {
return errors.StructuralError("subkey signature with wrong type")
}
if err := e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, sig); err != nil {
return errors.StructuralError("subkey signature invalid: " + err.Error())
}
switch sig.SigType {
case packet.SigTypeSubkeyRevocation:
subKey.Sig = sig
case packet.SigTypeSubkeyBinding:
if shouldReplaceSubkeySig(subKey.Sig, sig) {
subKey.Sig = sig
}
}
}
if subKey.Sig == nil {
return errors.StructuralError("subkey packet not followed by signature")
}
e.Subkeys = append(e.Subkeys, subKey)
return nil
}
func shouldReplaceSubkeySig(existingSig, potentialNewSig *packet.Signature) bool {
if potentialNewSig == nil {
return false
}
if existingSig == nil {
return true
}
if existingSig.SigType == packet.SigTypeSubkeyRevocation {
return false // never override a revocation signature
}
return potentialNewSig.CreationTime.After(existingSig.CreationTime)
}
const defaultRSAKeyBits = 2048
// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a
// single identity composed of the given full name, comment and email, any of
// which may be empty but must not contain any of "()<>\x00".
// If config is nil, sensible defaults will be used.
func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) {
creationTime := config.Now()
bits := defaultRSAKeyBits
if config != nil && config.RSABits != 0 {
bits = config.RSABits
}
uid := packet.NewUserId(name, comment, email)
if uid == nil {
return nil, errors.InvalidArgumentError("user id field contained invalid characters")
}
signingPriv, err := rsa.GenerateKey(config.Random(), bits)
if err != nil {
return nil, err
}
encryptingPriv, err := rsa.GenerateKey(config.Random(), bits)
if err != nil {
return nil, err
}
e := &Entity{
PrimaryKey: packet.NewRSAPublicKey(creationTime, &signingPriv.PublicKey),
PrivateKey: packet.NewRSAPrivateKey(creationTime, signingPriv),
Identities: make(map[string]*Identity),
}
isPrimaryId := true
e.Identities[uid.Id] = &Identity{
Name: uid.Id,
UserId: uid,
SelfSignature: &packet.Signature{
CreationTime: creationTime,
SigType: packet.SigTypePositiveCert,
PubKeyAlgo: packet.PubKeyAlgoRSA,
Hash: config.Hash(),
IsPrimaryId: &isPrimaryId,
FlagsValid: true,
FlagSign: true,
FlagCertify: true,
IssuerKeyId: &e.PrimaryKey.KeyId,
},
}
err = e.Identities[uid.Id].SelfSignature.SignUserId(uid.Id, e.PrimaryKey, e.PrivateKey, config)
if err != nil {
return nil, err
}
// If the user passes in a DefaultHash via packet.Config,
// set the PreferredHash for the SelfSignature.
if config != nil && config.DefaultHash != 0 {
e.Identities[uid.Id].SelfSignature.PreferredHash = []uint8{hashToHashId(config.DefaultHash)}
}
// Likewise for DefaultCipher.
if config != nil && config.DefaultCipher != 0 {
e.Identities[uid.Id].SelfSignature.PreferredSymmetric = []uint8{uint8(config.DefaultCipher)}
}
e.Subkeys = make([]Subkey, 1)
e.Subkeys[0] = Subkey{
PublicKey: packet.NewRSAPublicKey(creationTime, &encryptingPriv.PublicKey),
PrivateKey: packet.NewRSAPrivateKey(creationTime, encryptingPriv),
Sig: &packet.Signature{
CreationTime: creationTime,
SigType: packet.SigTypeSubkeyBinding,
PubKeyAlgo: packet.PubKeyAlgoRSA,
Hash: config.Hash(),
FlagsValid: true,
FlagEncryptStorage: true,
FlagEncryptCommunications: true,
IssuerKeyId: &e.PrimaryKey.KeyId,
},
}
e.Subkeys[0].PublicKey.IsSubkey = true
e.Subkeys[0].PrivateKey.IsSubkey = true
err = e.Subkeys[0].Sig.SignKey(e.Subkeys[0].PublicKey, e.PrivateKey, config)
if err != nil {
return nil, err
}
return e, nil
}
// SerializePrivate serializes an Entity, including private key material, but
// excluding signatures from other entities, to the given Writer.
// Identities and subkeys are re-signed in case they changed since NewEntry.
// If config is nil, sensible defaults will be used.
func (e *Entity) SerializePrivate(w io.Writer, config *packet.Config) (err error) {
err = e.PrivateKey.Serialize(w)
if err != nil {
return
}
for _, ident := range e.Identities {
err = ident.UserId.Serialize(w)
if err != nil {
return
}
err = ident.SelfSignature.SignUserId(ident.UserId.Id, e.PrimaryKey, e.PrivateKey, config)
if err != nil {
return
}
err = ident.SelfSignature.Serialize(w)
if err != nil {
return
}
}
for _, subkey := range e.Subkeys {
err = subkey.PrivateKey.Serialize(w)
if err != nil {
return
}
err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
if err != nil {
return
}
err = subkey.Sig.Serialize(w)
if err != nil {
return
}
}
return nil
}
// Serialize writes the public part of the given Entity to w, including
// signatures from other entities. No private key material will be output.
func (e *Entity) Serialize(w io.Writer) error {
err := e.PrimaryKey.Serialize(w)
if err != nil {
return err
}
for _, ident := range e.Identities {
err = ident.UserId.Serialize(w)
if err != nil {
return err
}
err = ident.SelfSignature.Serialize(w)
if err != nil {
return err
}
for _, sig := range ident.Signatures {
err = sig.Serialize(w)
if err != nil {
return err
}
}
}
for _, subkey := range e.Subkeys {
err = subkey.PublicKey.Serialize(w)
if err != nil {
return err
}
err = subkey.Sig.Serialize(w)
if err != nil {
return err
}
}
return nil
}
// SignIdentity adds a signature to e, from signer, attesting that identity is
// associated with e. The provided identity must already be an element of
// e.Identities and the private key of signer must have been decrypted if
// necessary.
// If config is nil, sensible defaults will be used.
func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Config) error {
if signer.PrivateKey == nil {
return errors.InvalidArgumentError("signing Entity must have a private key")
}
if signer.PrivateKey.Encrypted {
return errors.InvalidArgumentError("signing Entity's private key must be decrypted")
}
ident, ok := e.Identities[identity]
if !ok {
return errors.InvalidArgumentError("given identity string not found in Entity")
}
sig := &packet.Signature{
SigType: packet.SigTypeGenericCert,
PubKeyAlgo: signer.PrivateKey.PubKeyAlgo,
Hash: config.Hash(),
CreationTime: config.Now(),
IssuerKeyId: &signer.PrivateKey.KeyId,
}
if err := sig.SignUserId(identity, e.PrimaryKey, signer.PrivateKey, config); err != nil {
return err
}
ident.Signatures = append(ident.Signatures, sig)
return nil
}

123
vendor/golang.org/x/crypto/openpgp/packet/compressed.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,123 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"compress/bzip2"
"compress/flate"
"compress/zlib"
"golang.org/x/crypto/openpgp/errors"
"io"
"strconv"
)
// Compressed represents a compressed OpenPGP packet. The decompressed contents
// will contain more OpenPGP packets. See RFC 4880, section 5.6.
type Compressed struct {
Body io.Reader
}
const (
NoCompression = flate.NoCompression
BestSpeed = flate.BestSpeed
BestCompression = flate.BestCompression
DefaultCompression = flate.DefaultCompression
)
// CompressionConfig contains compressor configuration settings.
type CompressionConfig struct {
// Level is the compression level to use. It must be set to
// between -1 and 9, with -1 causing the compressor to use the
// default compression level, 0 causing the compressor to use
// no compression and 1 to 9 representing increasing (better,
// slower) compression levels. If Level is less than -1 or
// more then 9, a non-nil error will be returned during
// encryption. See the constants above for convenient common
// settings for Level.
Level int
}
func (c *Compressed) parse(r io.Reader) error {
var buf [1]byte
_, err := readFull(r, buf[:])
if err != nil {
return err
}
switch buf[0] {
case 1:
c.Body = flate.NewReader(r)
case 2:
c.Body, err = zlib.NewReader(r)
case 3:
c.Body = bzip2.NewReader(r)
default:
err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
}
return err
}
// compressedWriterCloser represents the serialized compression stream
// header and the compressor. Its Close() method ensures that both the
// compressor and serialized stream header are closed. Its Write()
// method writes to the compressor.
type compressedWriteCloser struct {
sh io.Closer // Stream Header
c io.WriteCloser // Compressor
}
func (cwc compressedWriteCloser) Write(p []byte) (int, error) {
return cwc.c.Write(p)
}
func (cwc compressedWriteCloser) Close() (err error) {
err = cwc.c.Close()
if err != nil {
return err
}
return cwc.sh.Close()
}
// SerializeCompressed serializes a compressed data packet to w and
// returns a WriteCloser to which the literal data packets themselves
// can be written and which MUST be closed on completion. If cc is
// nil, sensible defaults will be used to configure the compression
// algorithm.
func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *CompressionConfig) (literaldata io.WriteCloser, err error) {
compressed, err := serializeStreamHeader(w, packetTypeCompressed)
if err != nil {
return
}
_, err = compressed.Write([]byte{uint8(algo)})
if err != nil {
return
}
level := DefaultCompression
if cc != nil {
level = cc.Level
}
var compressor io.WriteCloser
switch algo {
case CompressionZIP:
compressor, err = flate.NewWriter(compressed, level)
case CompressionZLIB:
compressor, err = zlib.NewWriterLevel(compressed, level)
default:
s := strconv.Itoa(int(algo))
err = errors.UnsupportedError("Unsupported compression algorithm: " + s)
}
if err != nil {
return
}
literaldata = compressedWriteCloser{compressed, compressor}
return
}

91
vendor/golang.org/x/crypto/openpgp/packet/config.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,91 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"crypto"
"crypto/rand"
"io"
"time"
)
// Config collects a number of parameters along with sensible defaults.
// A nil *Config is valid and results in all default values.
type Config struct {
// Rand provides the source of entropy.
// If nil, the crypto/rand Reader is used.
Rand io.Reader
// DefaultHash is the default hash function to be used.
// If zero, SHA-256 is used.
DefaultHash crypto.Hash
// DefaultCipher is the cipher to be used.
// If zero, AES-128 is used.
DefaultCipher CipherFunction
// Time returns the current time as the number of seconds since the
// epoch. If Time is nil, time.Now is used.
Time func() time.Time
// DefaultCompressionAlgo is the compression algorithm to be
// applied to the plaintext before encryption. If zero, no
// compression is done.
DefaultCompressionAlgo CompressionAlgo
// CompressionConfig configures the compression settings.
CompressionConfig *CompressionConfig
// S2KCount is only used for symmetric encryption. It
// determines the strength of the passphrase stretching when
// the said passphrase is hashed to produce a key. S2KCount
// should be between 1024 and 65011712, inclusive. If Config
// is nil or S2KCount is 0, the value 65536 used. Not all
// values in the above range can be represented. S2KCount will
// be rounded up to the next representable value if it cannot
// be encoded exactly. When set, it is strongly encrouraged to
// use a value that is at least 65536. See RFC 4880 Section
// 3.7.1.3.
S2KCount int
// RSABits is the number of bits in new RSA keys made with NewEntity.
// If zero, then 2048 bit keys are created.
RSABits int
}
func (c *Config) Random() io.Reader {
if c == nil || c.Rand == nil {
return rand.Reader
}
return c.Rand
}
func (c *Config) Hash() crypto.Hash {
if c == nil || uint(c.DefaultHash) == 0 {
return crypto.SHA256
}
return c.DefaultHash
}
func (c *Config) Cipher() CipherFunction {
if c == nil || uint8(c.DefaultCipher) == 0 {
return CipherAES128
}
return c.DefaultCipher
}
func (c *Config) Now() time.Time {
if c == nil || c.Time == nil {
return time.Now()
}
return c.Time()
}
func (c *Config) Compression() CompressionAlgo {
if c == nil {
return CompressionNone
}
return c.DefaultCompressionAlgo
}
func (c *Config) PasswordHashIterations() int {
if c == nil || c.S2KCount == 0 {
return 0
}
return c.S2KCount
}

206
vendor/golang.org/x/crypto/openpgp/packet/encrypted_key.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,206 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"crypto/rsa"
"encoding/binary"
"io"
"math/big"
"strconv"
"golang.org/x/crypto/openpgp/elgamal"
"golang.org/x/crypto/openpgp/errors"
)
const encryptedKeyVersion = 3
// EncryptedKey represents a public-key encrypted session key. See RFC 4880,
// section 5.1.
type EncryptedKey struct {
KeyId uint64
Algo PublicKeyAlgorithm
CipherFunc CipherFunction // only valid after a successful Decrypt
Key []byte // only valid after a successful Decrypt
encryptedMPI1, encryptedMPI2 parsedMPI
}
func (e *EncryptedKey) parse(r io.Reader) (err error) {
var buf [10]byte
_, err = readFull(r, buf[:])
if err != nil {
return
}
if buf[0] != encryptedKeyVersion {
return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0])))
}
e.KeyId = binary.BigEndian.Uint64(buf[1:9])
e.Algo = PublicKeyAlgorithm(buf[9])
switch e.Algo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
e.encryptedMPI1.bytes, e.encryptedMPI1.bitLength, err = readMPI(r)
if err != nil {
return
}
case PubKeyAlgoElGamal:
e.encryptedMPI1.bytes, e.encryptedMPI1.bitLength, err = readMPI(r)
if err != nil {
return
}
e.encryptedMPI2.bytes, e.encryptedMPI2.bitLength, err = readMPI(r)
if err != nil {
return
}
}
_, err = consumeAll(r)
return
}
func checksumKeyMaterial(key []byte) uint16 {
var checksum uint16
for _, v := range key {
checksum += uint16(v)
}
return checksum
}
// Decrypt decrypts an encrypted session key with the given private key. The
// private key must have been decrypted first.
// If config is nil, sensible defaults will be used.
func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
var err error
var b []byte
// TODO(agl): use session key decryption routines here to avoid
// padding oracle attacks.
switch priv.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
k := priv.PrivateKey.(*rsa.PrivateKey)
b, err = rsa.DecryptPKCS1v15(config.Random(), k, padToKeySize(&k.PublicKey, e.encryptedMPI1.bytes))
case PubKeyAlgoElGamal:
c1 := new(big.Int).SetBytes(e.encryptedMPI1.bytes)
c2 := new(big.Int).SetBytes(e.encryptedMPI2.bytes)
b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2)
default:
err = errors.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
}
if err != nil {
return err
}
e.CipherFunc = CipherFunction(b[0])
e.Key = b[1 : len(b)-2]
expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
checksum := checksumKeyMaterial(e.Key)
if checksum != expectedChecksum {
return errors.StructuralError("EncryptedKey checksum incorrect")
}
return nil
}
// Serialize writes the encrypted key packet, e, to w.
func (e *EncryptedKey) Serialize(w io.Writer) error {
var mpiLen int
switch e.Algo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
mpiLen = 2 + len(e.encryptedMPI1.bytes)
case PubKeyAlgoElGamal:
mpiLen = 2 + len(e.encryptedMPI1.bytes) + 2 + len(e.encryptedMPI2.bytes)
default:
return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo)))
}
serializeHeader(w, packetTypeEncryptedKey, 1 /* version */ +8 /* key id */ +1 /* algo */ +mpiLen)
w.Write([]byte{encryptedKeyVersion})
binary.Write(w, binary.BigEndian, e.KeyId)
w.Write([]byte{byte(e.Algo)})
switch e.Algo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
writeMPIs(w, e.encryptedMPI1)
case PubKeyAlgoElGamal:
writeMPIs(w, e.encryptedMPI1, e.encryptedMPI2)
default:
panic("internal error")
}
return nil
}
// SerializeEncryptedKey serializes an encrypted key packet to w that contains
// key, encrypted to pub.
// If config is nil, sensible defaults will be used.
func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error {
var buf [10]byte
buf[0] = encryptedKeyVersion
binary.BigEndian.PutUint64(buf[1:9], pub.KeyId)
buf[9] = byte(pub.PubKeyAlgo)
keyBlock := make([]byte, 1 /* cipher type */ +len(key)+2 /* checksum */)
keyBlock[0] = byte(cipherFunc)
copy(keyBlock[1:], key)
checksum := checksumKeyMaterial(key)
keyBlock[1+len(key)] = byte(checksum >> 8)
keyBlock[1+len(key)+1] = byte(checksum)
switch pub.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
return serializeEncryptedKeyRSA(w, config.Random(), buf, pub.PublicKey.(*rsa.PublicKey), keyBlock)
case PubKeyAlgoElGamal:
return serializeEncryptedKeyElGamal(w, config.Random(), buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock)
case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly:
return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
}
return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
}
func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error {
cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)
if err != nil {
return errors.InvalidArgumentError("RSA encryption failed: " + err.Error())
}
packetLen := 10 /* header length */ + 2 /* mpi size */ + len(cipherText)
err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
if err != nil {
return err
}
_, err = w.Write(header[:])
if err != nil {
return err
}
return writeMPI(w, 8*uint16(len(cipherText)), cipherText)
}
func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error {
c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock)
if err != nil {
return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error())
}
packetLen := 10 /* header length */
packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8
packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8
err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
if err != nil {
return err
}
_, err = w.Write(header[:])
if err != nil {
return err
}
err = writeBig(w, c1)
if err != nil {
return err
}
return writeBig(w, c2)
}

89
vendor/golang.org/x/crypto/openpgp/packet/literal.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,89 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"encoding/binary"
"io"
)
// LiteralData represents an encrypted file. See RFC 4880, section 5.9.
type LiteralData struct {
IsBinary bool
FileName string
Time uint32 // Unix epoch time. Either creation time or modification time. 0 means undefined.
Body io.Reader
}
// ForEyesOnly returns whether the contents of the LiteralData have been marked
// as especially sensitive.
func (l *LiteralData) ForEyesOnly() bool {
return l.FileName == "_CONSOLE"
}
func (l *LiteralData) parse(r io.Reader) (err error) {
var buf [256]byte
_, err = readFull(r, buf[:2])
if err != nil {
return
}
l.IsBinary = buf[0] == 'b'
fileNameLen := int(buf[1])
_, err = readFull(r, buf[:fileNameLen])
if err != nil {
return
}
l.FileName = string(buf[:fileNameLen])
_, err = readFull(r, buf[:4])
if err != nil {
return
}
l.Time = binary.BigEndian.Uint32(buf[:4])
l.Body = r
return
}
// SerializeLiteral serializes a literal data packet to w and returns a
// WriteCloser to which the data itself can be written and which MUST be closed
// on completion. The fileName is truncated to 255 bytes.
func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) {
var buf [4]byte
buf[0] = 't'
if isBinary {
buf[0] = 'b'
}
if len(fileName) > 255 {
fileName = fileName[:255]
}
buf[1] = byte(len(fileName))
inner, err := serializeStreamHeader(w, packetTypeLiteralData)
if err != nil {
return
}
_, err = inner.Write(buf[:2])
if err != nil {
return
}
_, err = inner.Write([]byte(fileName))
if err != nil {
return
}
binary.BigEndian.PutUint32(buf[:], time)
_, err = inner.Write(buf[:])
if err != nil {
return
}
plaintext = inner
return
}

143
vendor/golang.org/x/crypto/openpgp/packet/ocfb.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,143 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// OpenPGP CFB Mode. http://tools.ietf.org/html/rfc4880#section-13.9
package packet
import (
"crypto/cipher"
)
type ocfbEncrypter struct {
b cipher.Block
fre []byte
outUsed int
}
// An OCFBResyncOption determines if the "resynchronization step" of OCFB is
// performed.
type OCFBResyncOption bool
const (
OCFBResync OCFBResyncOption = true
OCFBNoResync OCFBResyncOption = false
)
// NewOCFBEncrypter returns a cipher.Stream which encrypts data with OpenPGP's
// cipher feedback mode using the given cipher.Block, and an initial amount of
// ciphertext. randData must be random bytes and be the same length as the
// cipher.Block's block size. Resync determines if the "resynchronization step"
// from RFC 4880, 13.9 step 7 is performed. Different parts of OpenPGP vary on
// this point.
func NewOCFBEncrypter(block cipher.Block, randData []byte, resync OCFBResyncOption) (cipher.Stream, []byte) {
blockSize := block.BlockSize()
if len(randData) != blockSize {
return nil, nil
}
x := &ocfbEncrypter{
b: block,
fre: make([]byte, blockSize),
outUsed: 0,
}
prefix := make([]byte, blockSize+2)
block.Encrypt(x.fre, x.fre)
for i := 0; i < blockSize; i++ {
prefix[i] = randData[i] ^ x.fre[i]
}
block.Encrypt(x.fre, prefix[:blockSize])
prefix[blockSize] = x.fre[0] ^ randData[blockSize-2]
prefix[blockSize+1] = x.fre[1] ^ randData[blockSize-1]
if resync {
block.Encrypt(x.fre, prefix[2:])
} else {
x.fre[0] = prefix[blockSize]
x.fre[1] = prefix[blockSize+1]
x.outUsed = 2
}
return x, prefix
}
func (x *ocfbEncrypter) XORKeyStream(dst, src []byte) {
for i := 0; i < len(src); i++ {
if x.outUsed == len(x.fre) {
x.b.Encrypt(x.fre, x.fre)
x.outUsed = 0
}
x.fre[x.outUsed] ^= src[i]
dst[i] = x.fre[x.outUsed]
x.outUsed++
}
}
type ocfbDecrypter struct {
b cipher.Block
fre []byte
outUsed int
}
// NewOCFBDecrypter returns a cipher.Stream which decrypts data with OpenPGP's
// cipher feedback mode using the given cipher.Block. Prefix must be the first
// blockSize + 2 bytes of the ciphertext, where blockSize is the cipher.Block's
// block size. If an incorrect key is detected then nil is returned. On
// successful exit, blockSize+2 bytes of decrypted data are written into
// prefix. Resync determines if the "resynchronization step" from RFC 4880,
// 13.9 step 7 is performed. Different parts of OpenPGP vary on this point.
func NewOCFBDecrypter(block cipher.Block, prefix []byte, resync OCFBResyncOption) cipher.Stream {
blockSize := block.BlockSize()
if len(prefix) != blockSize+2 {
return nil
}
x := &ocfbDecrypter{
b: block,
fre: make([]byte, blockSize),
outUsed: 0,
}
prefixCopy := make([]byte, len(prefix))
copy(prefixCopy, prefix)
block.Encrypt(x.fre, x.fre)
for i := 0; i < blockSize; i++ {
prefixCopy[i] ^= x.fre[i]
}
block.Encrypt(x.fre, prefix[:blockSize])
prefixCopy[blockSize] ^= x.fre[0]
prefixCopy[blockSize+1] ^= x.fre[1]
if prefixCopy[blockSize-2] != prefixCopy[blockSize] ||
prefixCopy[blockSize-1] != prefixCopy[blockSize+1] {
return nil
}
if resync {
block.Encrypt(x.fre, prefix[2:])
} else {
x.fre[0] = prefix[blockSize]
x.fre[1] = prefix[blockSize+1]
x.outUsed = 2
}
copy(prefix, prefixCopy)
return x
}
func (x *ocfbDecrypter) XORKeyStream(dst, src []byte) {
for i := 0; i < len(src); i++ {
if x.outUsed == len(x.fre) {
x.b.Encrypt(x.fre, x.fre)
x.outUsed = 0
}
c := src[i]
dst[i] = x.fre[x.outUsed] ^ src[i]
x.fre[x.outUsed] = c
x.outUsed++
}
}

73
vendor/golang.org/x/crypto/openpgp/packet/one_pass_signature.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,73 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"crypto"
"encoding/binary"
"golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/s2k"
"io"
"strconv"
)
// OnePassSignature represents a one-pass signature packet. See RFC 4880,
// section 5.4.
type OnePassSignature struct {
SigType SignatureType
Hash crypto.Hash
PubKeyAlgo PublicKeyAlgorithm
KeyId uint64
IsLast bool
}
const onePassSignatureVersion = 3
func (ops *OnePassSignature) parse(r io.Reader) (err error) {
var buf [13]byte
_, err = readFull(r, buf[:])
if err != nil {
return
}
if buf[0] != onePassSignatureVersion {
err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
}
var ok bool
ops.Hash, ok = s2k.HashIdToHash(buf[2])
if !ok {
return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
}
ops.SigType = SignatureType(buf[1])
ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3])
ops.KeyId = binary.BigEndian.Uint64(buf[4:12])
ops.IsLast = buf[12] != 0
return
}
// Serialize marshals the given OnePassSignature to w.
func (ops *OnePassSignature) Serialize(w io.Writer) error {
var buf [13]byte
buf[0] = onePassSignatureVersion
buf[1] = uint8(ops.SigType)
var ok bool
buf[2], ok = s2k.HashToHashId(ops.Hash)
if !ok {
return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
}
buf[3] = uint8(ops.PubKeyAlgo)
binary.BigEndian.PutUint64(buf[4:12], ops.KeyId)
if ops.IsLast {
buf[12] = 1
}
if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil {
return err
}
_, err := w.Write(buf[:])
return err
}

162
vendor/golang.org/x/crypto/openpgp/packet/opaque.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,162 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"bytes"
"io"
"io/ioutil"
"golang.org/x/crypto/openpgp/errors"
)
// OpaquePacket represents an OpenPGP packet as raw, unparsed data. This is
// useful for splitting and storing the original packet contents separately,
// handling unsupported packet types or accessing parts of the packet not yet
// implemented by this package.
type OpaquePacket struct {
// Packet type
Tag uint8
// Reason why the packet was parsed opaquely
Reason error
// Binary contents of the packet data
Contents []byte
}
func (op *OpaquePacket) parse(r io.Reader) (err error) {
op.Contents, err = ioutil.ReadAll(r)
return
}
// Serialize marshals the packet to a writer in its original form, including
// the packet header.
func (op *OpaquePacket) Serialize(w io.Writer) (err error) {
err = serializeHeader(w, packetType(op.Tag), len(op.Contents))
if err == nil {
_, err = w.Write(op.Contents)
}
return
}
// Parse attempts to parse the opaque contents into a structure supported by
// this package. If the packet is not known then the result will be another
// OpaquePacket.
func (op *OpaquePacket) Parse() (p Packet, err error) {
hdr := bytes.NewBuffer(nil)
err = serializeHeader(hdr, packetType(op.Tag), len(op.Contents))
if err != nil {
op.Reason = err
return op, err
}
p, err = Read(io.MultiReader(hdr, bytes.NewBuffer(op.Contents)))
if err != nil {
op.Reason = err
p = op
}
return
}
// OpaqueReader reads OpaquePackets from an io.Reader.
type OpaqueReader struct {
r io.Reader
}
func NewOpaqueReader(r io.Reader) *OpaqueReader {
return &OpaqueReader{r: r}
}
// Read the next OpaquePacket.
func (or *OpaqueReader) Next() (op *OpaquePacket, err error) {
tag, _, contents, err := readHeader(or.r)
if err != nil {
return
}
op = &OpaquePacket{Tag: uint8(tag), Reason: err}
err = op.parse(contents)
if err != nil {
consumeAll(contents)
}
return
}
// OpaqueSubpacket represents an unparsed OpenPGP subpacket,
// as found in signature and user attribute packets.
type OpaqueSubpacket struct {
SubType uint8
Contents []byte
}
// OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from
// their byte representation.
func OpaqueSubpackets(contents []byte) (result []*OpaqueSubpacket, err error) {
var (
subHeaderLen int
subPacket *OpaqueSubpacket
)
for len(contents) > 0 {
subHeaderLen, subPacket, err = nextSubpacket(contents)
if err != nil {
break
}
result = append(result, subPacket)
contents = contents[subHeaderLen+len(subPacket.Contents):]
}
return
}
func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) {
// RFC 4880, section 5.2.3.1
var subLen uint32
if len(contents) < 1 {
goto Truncated
}
subPacket = &OpaqueSubpacket{}
switch {
case contents[0] < 192:
subHeaderLen = 2 // 1 length byte, 1 subtype byte
if len(contents) < subHeaderLen {
goto Truncated
}
subLen = uint32(contents[0])
contents = contents[1:]
case contents[0] < 255:
subHeaderLen = 3 // 2 length bytes, 1 subtype
if len(contents) < subHeaderLen {
goto Truncated
}
subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192
contents = contents[2:]
default:
subHeaderLen = 6 // 5 length bytes, 1 subtype
if len(contents) < subHeaderLen {
goto Truncated
}
subLen = uint32(contents[1])<<24 |
uint32(contents[2])<<16 |
uint32(contents[3])<<8 |
uint32(contents[4])
contents = contents[5:]
}
if subLen > uint32(len(contents)) || subLen == 0 {
goto Truncated
}
subPacket.SubType = contents[0]
subPacket.Contents = contents[1:subLen]
return
Truncated:
err = errors.StructuralError("subpacket truncated")
return
}
func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) {
buf := make([]byte, 6)
n := serializeSubpacketLength(buf, len(osp.Contents)+1)
buf[n] = osp.SubType
if _, err = w.Write(buf[:n+1]); err != nil {
return
}
_, err = w.Write(osp.Contents)
return
}

551
vendor/golang.org/x/crypto/openpgp/packet/packet.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,551 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package packet implements parsing and serialization of OpenPGP packets, as
// specified in RFC 4880.
package packet // import "golang.org/x/crypto/openpgp/packet"
import (
"bufio"
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/rsa"
"io"
"math/big"
"golang.org/x/crypto/cast5"
"golang.org/x/crypto/openpgp/errors"
)
// readFull is the same as io.ReadFull except that reading zero bytes returns
// ErrUnexpectedEOF rather than EOF.
func readFull(r io.Reader, buf []byte) (n int, err error) {
n, err = io.ReadFull(r, buf)
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return
}
// readLength reads an OpenPGP length from r. See RFC 4880, section 4.2.2.
func readLength(r io.Reader) (length int64, isPartial bool, err error) {
var buf [4]byte
_, err = readFull(r, buf[:1])
if err != nil {
return
}
switch {
case buf[0] < 192:
length = int64(buf[0])
case buf[0] < 224:
length = int64(buf[0]-192) << 8
_, err = readFull(r, buf[0:1])
if err != nil {
return
}
length += int64(buf[0]) + 192
case buf[0] < 255:
length = int64(1) << (buf[0] & 0x1f)
isPartial = true
default:
_, err = readFull(r, buf[0:4])
if err != nil {
return
}
length = int64(buf[0])<<24 |
int64(buf[1])<<16 |
int64(buf[2])<<8 |
int64(buf[3])
}
return
}
// partialLengthReader wraps an io.Reader and handles OpenPGP partial lengths.
// The continuation lengths are parsed and removed from the stream and EOF is
// returned at the end of the packet. See RFC 4880, section 4.2.2.4.
type partialLengthReader struct {
r io.Reader
remaining int64
isPartial bool
}
func (r *partialLengthReader) Read(p []byte) (n int, err error) {
for r.remaining == 0 {
if !r.isPartial {
return 0, io.EOF
}
r.remaining, r.isPartial, err = readLength(r.r)
if err != nil {
return 0, err
}
}
toRead := int64(len(p))
if toRead > r.remaining {
toRead = r.remaining
}
n, err = r.r.Read(p[:int(toRead)])
r.remaining -= int64(n)
if n < int(toRead) && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return
}
// partialLengthWriter writes a stream of data using OpenPGP partial lengths.
// See RFC 4880, section 4.2.2.4.
type partialLengthWriter struct {
w io.WriteCloser
lengthByte [1]byte
}
func (w *partialLengthWriter) Write(p []byte) (n int, err error) {
for len(p) > 0 {
for power := uint(14); power < 32; power-- {
l := 1 << power
if len(p) >= l {
w.lengthByte[0] = 224 + uint8(power)
_, err = w.w.Write(w.lengthByte[:])
if err != nil {
return
}
var m int
m, err = w.w.Write(p[:l])
n += m
if err != nil {
return
}
p = p[l:]
break
}
}
}
return
}
func (w *partialLengthWriter) Close() error {
w.lengthByte[0] = 0
_, err := w.w.Write(w.lengthByte[:])
if err != nil {
return err
}
return w.w.Close()
}
// A spanReader is an io.LimitReader, but it returns ErrUnexpectedEOF if the
// underlying Reader returns EOF before the limit has been reached.
type spanReader struct {
r io.Reader
n int64
}
func (l *spanReader) Read(p []byte) (n int, err error) {
if l.n <= 0 {
return 0, io.EOF
}
if int64(len(p)) > l.n {
p = p[0:l.n]
}
n, err = l.r.Read(p)
l.n -= int64(n)
if l.n > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return
}
// readHeader parses a packet header and returns an io.Reader which will return
// the contents of the packet. See RFC 4880, section 4.2.
func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader, err error) {
var buf [4]byte
_, err = io.ReadFull(r, buf[:1])
if err != nil {
return
}
if buf[0]&0x80 == 0 {
err = errors.StructuralError("tag byte does not have MSB set")
return
}
if buf[0]&0x40 == 0 {
// Old format packet
tag = packetType((buf[0] & 0x3f) >> 2)
lengthType := buf[0] & 3
if lengthType == 3 {
length = -1
contents = r
return
}
lengthBytes := 1 << lengthType
_, err = readFull(r, buf[0:lengthBytes])
if err != nil {
return
}
for i := 0; i < lengthBytes; i++ {
length <<= 8
length |= int64(buf[i])
}
contents = &spanReader{r, length}
return
}
// New format packet
tag = packetType(buf[0] & 0x3f)
length, isPartial, err := readLength(r)
if err != nil {
return
}
if isPartial {
contents = &partialLengthReader{
remaining: length,
isPartial: true,
r: r,
}
length = -1
} else {
contents = &spanReader{r, length}
}
return
}
// serializeHeader writes an OpenPGP packet header to w. See RFC 4880, section
// 4.2.
func serializeHeader(w io.Writer, ptype packetType, length int) (err error) {
var buf [6]byte
var n int
buf[0] = 0x80 | 0x40 | byte(ptype)
if length < 192 {
buf[1] = byte(length)
n = 2
} else if length < 8384 {
length -= 192
buf[1] = 192 + byte(length>>8)
buf[2] = byte(length)
n = 3
} else {
buf[1] = 255
buf[2] = byte(length >> 24)
buf[3] = byte(length >> 16)
buf[4] = byte(length >> 8)
buf[5] = byte(length)
n = 6
}
_, err = w.Write(buf[:n])
return
}
// serializeStreamHeader writes an OpenPGP packet header to w where the
// length of the packet is unknown. It returns a io.WriteCloser which can be
// used to write the contents of the packet. See RFC 4880, section 4.2.
func serializeStreamHeader(w io.WriteCloser, ptype packetType) (out io.WriteCloser, err error) {
var buf [1]byte
buf[0] = 0x80 | 0x40 | byte(ptype)
_, err = w.Write(buf[:])
if err != nil {
return
}
out = &partialLengthWriter{w: w}
return
}
// Packet represents an OpenPGP packet. Users are expected to try casting
// instances of this interface to specific packet types.
type Packet interface {
parse(io.Reader) error
}
// consumeAll reads from the given Reader until error, returning the number of
// bytes read.
func consumeAll(r io.Reader) (n int64, err error) {
var m int
var buf [1024]byte
for {
m, err = r.Read(buf[:])
n += int64(m)
if err == io.EOF {
err = nil
return
}
if err != nil {
return
}
}
}
// packetType represents the numeric ids of the different OpenPGP packet types. See
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-2
type packetType uint8
const (
packetTypeEncryptedKey packetType = 1
packetTypeSignature packetType = 2
packetTypeSymmetricKeyEncrypted packetType = 3
packetTypeOnePassSignature packetType = 4
packetTypePrivateKey packetType = 5
packetTypePublicKey packetType = 6
packetTypePrivateSubkey packetType = 7
packetTypeCompressed packetType = 8
packetTypeSymmetricallyEncrypted packetType = 9
packetTypeLiteralData packetType = 11
packetTypeUserId packetType = 13
packetTypePublicSubkey packetType = 14
packetTypeUserAttribute packetType = 17
packetTypeSymmetricallyEncryptedMDC packetType = 18
)
// peekVersion detects the version of a public key packet about to
// be read. A bufio.Reader at the original position of the io.Reader
// is returned.
func peekVersion(r io.Reader) (bufr *bufio.Reader, ver byte, err error) {
bufr = bufio.NewReader(r)
var verBuf []byte
if verBuf, err = bufr.Peek(1); err != nil {
return
}
ver = verBuf[0]
return
}
// Read reads a single OpenPGP packet from the given io.Reader. If there is an
// error parsing a packet, the whole packet is consumed from the input.
func Read(r io.Reader) (p Packet, err error) {
tag, _, contents, err := readHeader(r)
if err != nil {
return
}
switch tag {
case packetTypeEncryptedKey:
p = new(EncryptedKey)
case packetTypeSignature:
var version byte
// Detect signature version
if contents, version, err = peekVersion(contents); err != nil {
return
}
if version < 4 {
p = new(SignatureV3)
} else {
p = new(Signature)
}
case packetTypeSymmetricKeyEncrypted:
p = new(SymmetricKeyEncrypted)
case packetTypeOnePassSignature:
p = new(OnePassSignature)
case packetTypePrivateKey, packetTypePrivateSubkey:
pk := new(PrivateKey)
if tag == packetTypePrivateSubkey {
pk.IsSubkey = true
}
p = pk
case packetTypePublicKey, packetTypePublicSubkey:
var version byte
if contents, version, err = peekVersion(contents); err != nil {
return
}
isSubkey := tag == packetTypePublicSubkey
if version < 4 {
p = &PublicKeyV3{IsSubkey: isSubkey}
} else {
p = &PublicKey{IsSubkey: isSubkey}
}
case packetTypeCompressed:
p = new(Compressed)
case packetTypeSymmetricallyEncrypted:
p = new(SymmetricallyEncrypted)
case packetTypeLiteralData:
p = new(LiteralData)
case packetTypeUserId:
p = new(UserId)
case packetTypeUserAttribute:
p = new(UserAttribute)
case packetTypeSymmetricallyEncryptedMDC:
se := new(SymmetricallyEncrypted)
se.MDC = true
p = se
default:
err = errors.UnknownPacketTypeError(tag)
}
if p != nil {
err = p.parse(contents)
}
if err != nil {
consumeAll(contents)
}
return
}
// SignatureType represents the different semantic meanings of an OpenPGP
// signature. See RFC 4880, section 5.2.1.
type SignatureType uint8
const (
SigTypeBinary SignatureType = 0
SigTypeText = 1
SigTypeGenericCert = 0x10
SigTypePersonaCert = 0x11
SigTypeCasualCert = 0x12
SigTypePositiveCert = 0x13
SigTypeSubkeyBinding = 0x18
SigTypePrimaryKeyBinding = 0x19
SigTypeDirectSignature = 0x1F
SigTypeKeyRevocation = 0x20
SigTypeSubkeyRevocation = 0x28
)
// PublicKeyAlgorithm represents the different public key system specified for
// OpenPGP. See
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-12
type PublicKeyAlgorithm uint8
const (
PubKeyAlgoRSA PublicKeyAlgorithm = 1
PubKeyAlgoElGamal PublicKeyAlgorithm = 16
PubKeyAlgoDSA PublicKeyAlgorithm = 17
// RFC 6637, Section 5.
PubKeyAlgoECDH PublicKeyAlgorithm = 18
PubKeyAlgoECDSA PublicKeyAlgorithm = 19
// Deprecated in RFC 4880, Section 13.5. Use key flags instead.
PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2
PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3
)
// CanEncrypt returns true if it's possible to encrypt a message to a public
// key of the given type.
func (pka PublicKeyAlgorithm) CanEncrypt() bool {
switch pka {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal:
return true
}
return false
}
// CanSign returns true if it's possible for a public key of the given type to
// sign a message.
func (pka PublicKeyAlgorithm) CanSign() bool {
switch pka {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA:
return true
}
return false
}
// CipherFunction represents the different block ciphers specified for OpenPGP. See
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13
type CipherFunction uint8
const (
Cipher3DES CipherFunction = 2
CipherCAST5 CipherFunction = 3
CipherAES128 CipherFunction = 7
CipherAES192 CipherFunction = 8
CipherAES256 CipherFunction = 9
)
// KeySize returns the key size, in bytes, of cipher.
func (cipher CipherFunction) KeySize() int {
switch cipher {
case Cipher3DES:
return 24
case CipherCAST5:
return cast5.KeySize
case CipherAES128:
return 16
case CipherAES192:
return 24
case CipherAES256:
return 32
}
return 0
}
// blockSize returns the block size, in bytes, of cipher.
func (cipher CipherFunction) blockSize() int {
switch cipher {
case Cipher3DES:
return des.BlockSize
case CipherCAST5:
return 8
case CipherAES128, CipherAES192, CipherAES256:
return 16
}
return 0
}
// new returns a fresh instance of the given cipher.
func (cipher CipherFunction) new(key []byte) (block cipher.Block) {
switch cipher {
case Cipher3DES:
block, _ = des.NewTripleDESCipher(key)
case CipherCAST5:
block, _ = cast5.NewCipher(key)
case CipherAES128, CipherAES192, CipherAES256:
block, _ = aes.NewCipher(key)
}
return
}
// readMPI reads a big integer from r. The bit length returned is the bit
// length that was specified in r. This is preserved so that the integer can be
// reserialized exactly.
func readMPI(r io.Reader) (mpi []byte, bitLength uint16, err error) {
var buf [2]byte
_, err = readFull(r, buf[0:])
if err != nil {
return
}
bitLength = uint16(buf[0])<<8 | uint16(buf[1])
numBytes := (int(bitLength) + 7) / 8
mpi = make([]byte, numBytes)
_, err = readFull(r, mpi)
// According to RFC 4880 3.2. we should check that the MPI has no leading
// zeroes (at least when not an encrypted MPI?), but this implementation
// does generate leading zeroes, so we keep accepting them.
return
}
// writeMPI serializes a big integer to w.
func writeMPI(w io.Writer, bitLength uint16, mpiBytes []byte) (err error) {
// Note that we can produce leading zeroes, in violation of RFC 4880 3.2.
// Implementations seem to be tolerant of them, and stripping them would
// make it complex to guarantee matching re-serialization.
_, err = w.Write([]byte{byte(bitLength >> 8), byte(bitLength)})
if err == nil {
_, err = w.Write(mpiBytes)
}
return
}
// writeBig serializes a *big.Int to w.
func writeBig(w io.Writer, i *big.Int) error {
return writeMPI(w, uint16(i.BitLen()), i.Bytes())
}
// padToKeySize left-pads a MPI with zeroes to match the length of the
// specified RSA public.
func padToKeySize(pub *rsa.PublicKey, b []byte) []byte {
k := (pub.N.BitLen() + 7) / 8
if len(b) >= k {
return b
}
bb := make([]byte, k)
copy(bb[len(bb)-len(b):], b)
return bb
}
// CompressionAlgo Represents the different compression algorithms
// supported by OpenPGP (except for BZIP2, which is not currently
// supported). See Section 9.3 of RFC 4880.
type CompressionAlgo uint8
const (
CompressionNone CompressionAlgo = 0
CompressionZIP CompressionAlgo = 1
CompressionZLIB CompressionAlgo = 2
)

385
vendor/golang.org/x/crypto/openpgp/packet/private_key.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,385 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"bytes"
"crypto"
"crypto/cipher"
"crypto/dsa"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha1"
"io"
"io/ioutil"
"math/big"
"strconv"
"time"
"golang.org/x/crypto/openpgp/elgamal"
"golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/s2k"
)
// PrivateKey represents a possibly encrypted private key. See RFC 4880,
// section 5.5.3.
type PrivateKey struct {
PublicKey
Encrypted bool // if true then the private key is unavailable until Decrypt has been called.
encryptedData []byte
cipher CipherFunction
s2k func(out, in []byte)
PrivateKey interface{} // An *{rsa|dsa|ecdsa}.PrivateKey or a crypto.Signer.
sha1Checksum bool
iv []byte
}
func NewRSAPrivateKey(creationTime time.Time, priv *rsa.PrivateKey) *PrivateKey {
pk := new(PrivateKey)
pk.PublicKey = *NewRSAPublicKey(creationTime, &priv.PublicKey)
pk.PrivateKey = priv
return pk
}
func NewDSAPrivateKey(creationTime time.Time, priv *dsa.PrivateKey) *PrivateKey {
pk := new(PrivateKey)
pk.PublicKey = *NewDSAPublicKey(creationTime, &priv.PublicKey)
pk.PrivateKey = priv
return pk
}
func NewElGamalPrivateKey(creationTime time.Time, priv *elgamal.PrivateKey) *PrivateKey {
pk := new(PrivateKey)
pk.PublicKey = *NewElGamalPublicKey(creationTime, &priv.PublicKey)
pk.PrivateKey = priv
return pk
}
func NewECDSAPrivateKey(creationTime time.Time, priv *ecdsa.PrivateKey) *PrivateKey {
pk := new(PrivateKey)
pk.PublicKey = *NewECDSAPublicKey(creationTime, &priv.PublicKey)
pk.PrivateKey = priv
return pk
}
// NewSignerPrivateKey creates a PrivateKey from a crypto.Signer that
// implements RSA or ECDSA.
func NewSignerPrivateKey(creationTime time.Time, signer crypto.Signer) *PrivateKey {
pk := new(PrivateKey)
// In general, the public Keys should be used as pointers. We still
// type-switch on the values, for backwards-compatibility.
switch pubkey := signer.Public().(type) {
case *rsa.PublicKey:
pk.PublicKey = *NewRSAPublicKey(creationTime, pubkey)
case rsa.PublicKey:
pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey)
case *ecdsa.PublicKey:
pk.PublicKey = *NewECDSAPublicKey(creationTime, pubkey)
case ecdsa.PublicKey:
pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey)
default:
panic("openpgp: unknown crypto.Signer type in NewSignerPrivateKey")
}
pk.PrivateKey = signer
return pk
}
func (pk *PrivateKey) parse(r io.Reader) (err error) {
err = (&pk.PublicKey).parse(r)
if err != nil {
return
}
var buf [1]byte
_, err = readFull(r, buf[:])
if err != nil {
return
}
s2kType := buf[0]
switch s2kType {
case 0:
pk.s2k = nil
pk.Encrypted = false
case 254, 255:
_, err = readFull(r, buf[:])
if err != nil {
return
}
pk.cipher = CipherFunction(buf[0])
pk.Encrypted = true
pk.s2k, err = s2k.Parse(r)
if err != nil {
return
}
if s2kType == 254 {
pk.sha1Checksum = true
}
default:
return errors.UnsupportedError("deprecated s2k function in private key")
}
if pk.Encrypted {
blockSize := pk.cipher.blockSize()
if blockSize == 0 {
return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher)))
}
pk.iv = make([]byte, blockSize)
_, err = readFull(r, pk.iv)
if err != nil {
return
}
}
pk.encryptedData, err = ioutil.ReadAll(r)
if err != nil {
return
}
if !pk.Encrypted {
return pk.parsePrivateKey(pk.encryptedData)
}
return
}
func mod64kHash(d []byte) uint16 {
var h uint16
for _, b := range d {
h += uint16(b)
}
return h
}
func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
// TODO(agl): support encrypted private keys
buf := bytes.NewBuffer(nil)
err = pk.PublicKey.serializeWithoutHeaders(buf)
if err != nil {
return
}
buf.WriteByte(0 /* no encryption */)
privateKeyBuf := bytes.NewBuffer(nil)
switch priv := pk.PrivateKey.(type) {
case *rsa.PrivateKey:
err = serializeRSAPrivateKey(privateKeyBuf, priv)
case *dsa.PrivateKey:
err = serializeDSAPrivateKey(privateKeyBuf, priv)
case *elgamal.PrivateKey:
err = serializeElGamalPrivateKey(privateKeyBuf, priv)
case *ecdsa.PrivateKey:
err = serializeECDSAPrivateKey(privateKeyBuf, priv)
default:
err = errors.InvalidArgumentError("unknown private key type")
}
if err != nil {
return
}
ptype := packetTypePrivateKey
contents := buf.Bytes()
privateKeyBytes := privateKeyBuf.Bytes()
if pk.IsSubkey {
ptype = packetTypePrivateSubkey
}
err = serializeHeader(w, ptype, len(contents)+len(privateKeyBytes)+2)
if err != nil {
return
}
_, err = w.Write(contents)
if err != nil {
return
}
_, err = w.Write(privateKeyBytes)
if err != nil {
return
}
checksum := mod64kHash(privateKeyBytes)
var checksumBytes [2]byte
checksumBytes[0] = byte(checksum >> 8)
checksumBytes[1] = byte(checksum)
_, err = w.Write(checksumBytes[:])
return
}
func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error {
err := writeBig(w, priv.D)
if err != nil {
return err
}
err = writeBig(w, priv.Primes[1])
if err != nil {
return err
}
err = writeBig(w, priv.Primes[0])
if err != nil {
return err
}
return writeBig(w, priv.Precomputed.Qinv)
}
func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error {
return writeBig(w, priv.X)
}
func serializeElGamalPrivateKey(w io.Writer, priv *elgamal.PrivateKey) error {
return writeBig(w, priv.X)
}
func serializeECDSAPrivateKey(w io.Writer, priv *ecdsa.PrivateKey) error {
return writeBig(w, priv.D)
}
// Decrypt decrypts an encrypted private key using a passphrase.
func (pk *PrivateKey) Decrypt(passphrase []byte) error {
if !pk.Encrypted {
return nil
}
key := make([]byte, pk.cipher.KeySize())
pk.s2k(key, passphrase)
block := pk.cipher.new(key)
cfb := cipher.NewCFBDecrypter(block, pk.iv)
data := make([]byte, len(pk.encryptedData))
cfb.XORKeyStream(data, pk.encryptedData)
if pk.sha1Checksum {
if len(data) < sha1.Size {
return errors.StructuralError("truncated private key data")
}
h := sha1.New()
h.Write(data[:len(data)-sha1.Size])
sum := h.Sum(nil)
if !bytes.Equal(sum, data[len(data)-sha1.Size:]) {
return errors.StructuralError("private key checksum failure")
}
data = data[:len(data)-sha1.Size]
} else {
if len(data) < 2 {
return errors.StructuralError("truncated private key data")
}
var sum uint16
for i := 0; i < len(data)-2; i++ {
sum += uint16(data[i])
}
if data[len(data)-2] != uint8(sum>>8) ||
data[len(data)-1] != uint8(sum) {
return errors.StructuralError("private key checksum failure")
}
data = data[:len(data)-2]
}
return pk.parsePrivateKey(data)
}
func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) {
switch pk.PublicKey.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoRSAEncryptOnly:
return pk.parseRSAPrivateKey(data)
case PubKeyAlgoDSA:
return pk.parseDSAPrivateKey(data)
case PubKeyAlgoElGamal:
return pk.parseElGamalPrivateKey(data)
case PubKeyAlgoECDSA:
return pk.parseECDSAPrivateKey(data)
}
panic("impossible")
}
func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) {
rsaPub := pk.PublicKey.PublicKey.(*rsa.PublicKey)
rsaPriv := new(rsa.PrivateKey)
rsaPriv.PublicKey = *rsaPub
buf := bytes.NewBuffer(data)
d, _, err := readMPI(buf)
if err != nil {
return
}
p, _, err := readMPI(buf)
if err != nil {
return
}
q, _, err := readMPI(buf)
if err != nil {
return
}
rsaPriv.D = new(big.Int).SetBytes(d)
rsaPriv.Primes = make([]*big.Int, 2)
rsaPriv.Primes[0] = new(big.Int).SetBytes(p)
rsaPriv.Primes[1] = new(big.Int).SetBytes(q)
if err := rsaPriv.Validate(); err != nil {
return err
}
rsaPriv.Precompute()
pk.PrivateKey = rsaPriv
pk.Encrypted = false
pk.encryptedData = nil
return nil
}
func (pk *PrivateKey) parseDSAPrivateKey(data []byte) (err error) {
dsaPub := pk.PublicKey.PublicKey.(*dsa.PublicKey)
dsaPriv := new(dsa.PrivateKey)
dsaPriv.PublicKey = *dsaPub
buf := bytes.NewBuffer(data)
x, _, err := readMPI(buf)
if err != nil {
return
}
dsaPriv.X = new(big.Int).SetBytes(x)
pk.PrivateKey = dsaPriv
pk.Encrypted = false
pk.encryptedData = nil
return nil
}
func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) {
pub := pk.PublicKey.PublicKey.(*elgamal.PublicKey)
priv := new(elgamal.PrivateKey)
priv.PublicKey = *pub
buf := bytes.NewBuffer(data)
x, _, err := readMPI(buf)
if err != nil {
return
}
priv.X = new(big.Int).SetBytes(x)
pk.PrivateKey = priv
pk.Encrypted = false
pk.encryptedData = nil
return nil
}
func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) {
ecdsaPub := pk.PublicKey.PublicKey.(*ecdsa.PublicKey)
buf := bytes.NewBuffer(data)
d, _, err := readMPI(buf)
if err != nil {
return
}
pk.PrivateKey = &ecdsa.PrivateKey{
PublicKey: *ecdsaPub,
D: new(big.Int).SetBytes(d),
}
pk.Encrypted = false
pk.encryptedData = nil
return nil
}

753
vendor/golang.org/x/crypto/openpgp/packet/public_key.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,753 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"bytes"
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
"encoding/binary"
"fmt"
"hash"
"io"
"math/big"
"strconv"
"time"
"golang.org/x/crypto/openpgp/elgamal"
"golang.org/x/crypto/openpgp/errors"
)
var (
// NIST curve P-256
oidCurveP256 []byte = []byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}
// NIST curve P-384
oidCurveP384 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x22}
// NIST curve P-521
oidCurveP521 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x23}
)
const maxOIDLength = 8
// ecdsaKey stores the algorithm-specific fields for ECDSA keys.
// as defined in RFC 6637, Section 9.
type ecdsaKey struct {
// oid contains the OID byte sequence identifying the elliptic curve used
oid []byte
// p contains the elliptic curve point that represents the public key
p parsedMPI
}
// parseOID reads the OID for the curve as defined in RFC 6637, Section 9.
func parseOID(r io.Reader) (oid []byte, err error) {
buf := make([]byte, maxOIDLength)
if _, err = readFull(r, buf[:1]); err != nil {
return
}
oidLen := buf[0]
if int(oidLen) > len(buf) {
err = errors.UnsupportedError("invalid oid length: " + strconv.Itoa(int(oidLen)))
return
}
oid = buf[:oidLen]
_, err = readFull(r, oid)
return
}
func (f *ecdsaKey) parse(r io.Reader) (err error) {
if f.oid, err = parseOID(r); err != nil {
return err
}
f.p.bytes, f.p.bitLength, err = readMPI(r)
return
}
func (f *ecdsaKey) serialize(w io.Writer) (err error) {
buf := make([]byte, maxOIDLength+1)
buf[0] = byte(len(f.oid))
copy(buf[1:], f.oid)
if _, err = w.Write(buf[:len(f.oid)+1]); err != nil {
return
}
return writeMPIs(w, f.p)
}
func (f *ecdsaKey) newECDSA() (*ecdsa.PublicKey, error) {
var c elliptic.Curve
if bytes.Equal(f.oid, oidCurveP256) {
c = elliptic.P256()
} else if bytes.Equal(f.oid, oidCurveP384) {
c = elliptic.P384()
} else if bytes.Equal(f.oid, oidCurveP521) {
c = elliptic.P521()
} else {
return nil, errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", f.oid))
}
x, y := elliptic.Unmarshal(c, f.p.bytes)
if x == nil {
return nil, errors.UnsupportedError("failed to parse EC point")
}
return &ecdsa.PublicKey{Curve: c, X: x, Y: y}, nil
}
func (f *ecdsaKey) byteLen() int {
return 1 + len(f.oid) + 2 + len(f.p.bytes)
}
type kdfHashFunction byte
type kdfAlgorithm byte
// ecdhKdf stores key derivation function parameters
// used for ECDH encryption. See RFC 6637, Section 9.
type ecdhKdf struct {
KdfHash kdfHashFunction
KdfAlgo kdfAlgorithm
}
func (f *ecdhKdf) parse(r io.Reader) (err error) {
buf := make([]byte, 1)
if _, err = readFull(r, buf); err != nil {
return
}
kdfLen := int(buf[0])
if kdfLen < 3 {
return errors.UnsupportedError("Unsupported ECDH KDF length: " + strconv.Itoa(kdfLen))
}
buf = make([]byte, kdfLen)
if _, err = readFull(r, buf); err != nil {
return
}
reserved := int(buf[0])
f.KdfHash = kdfHashFunction(buf[1])
f.KdfAlgo = kdfAlgorithm(buf[2])
if reserved != 0x01 {
return errors.UnsupportedError("Unsupported KDF reserved field: " + strconv.Itoa(reserved))
}
return
}
func (f *ecdhKdf) serialize(w io.Writer) (err error) {
buf := make([]byte, 4)
// See RFC 6637, Section 9, Algorithm-Specific Fields for ECDH keys.
buf[0] = byte(0x03) // Length of the following fields
buf[1] = byte(0x01) // Reserved for future extensions, must be 1 for now
buf[2] = byte(f.KdfHash)
buf[3] = byte(f.KdfAlgo)
_, err = w.Write(buf[:])
return
}
func (f *ecdhKdf) byteLen() int {
return 4
}
// PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2.
type PublicKey struct {
CreationTime time.Time
PubKeyAlgo PublicKeyAlgorithm
PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey or *ecdsa.PublicKey
Fingerprint [20]byte
KeyId uint64
IsSubkey bool
n, e, p, q, g, y parsedMPI
// RFC 6637 fields
ec *ecdsaKey
ecdh *ecdhKdf
}
// signingKey provides a convenient abstraction over signature verification
// for v3 and v4 public keys.
type signingKey interface {
SerializeSignaturePrefix(io.Writer)
serializeWithoutHeaders(io.Writer) error
}
func fromBig(n *big.Int) parsedMPI {
return parsedMPI{
bytes: n.Bytes(),
bitLength: uint16(n.BitLen()),
}
}
// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey {
pk := &PublicKey{
CreationTime: creationTime,
PubKeyAlgo: PubKeyAlgoRSA,
PublicKey: pub,
n: fromBig(pub.N),
e: fromBig(big.NewInt(int64(pub.E))),
}
pk.setFingerPrintAndKeyId()
return pk
}
// NewDSAPublicKey returns a PublicKey that wraps the given dsa.PublicKey.
func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey {
pk := &PublicKey{
CreationTime: creationTime,
PubKeyAlgo: PubKeyAlgoDSA,
PublicKey: pub,
p: fromBig(pub.P),
q: fromBig(pub.Q),
g: fromBig(pub.G),
y: fromBig(pub.Y),
}
pk.setFingerPrintAndKeyId()
return pk
}
// NewElGamalPublicKey returns a PublicKey that wraps the given elgamal.PublicKey.
func NewElGamalPublicKey(creationTime time.Time, pub *elgamal.PublicKey) *PublicKey {
pk := &PublicKey{
CreationTime: creationTime,
PubKeyAlgo: PubKeyAlgoElGamal,
PublicKey: pub,
p: fromBig(pub.P),
g: fromBig(pub.G),
y: fromBig(pub.Y),
}
pk.setFingerPrintAndKeyId()
return pk
}
func NewECDSAPublicKey(creationTime time.Time, pub *ecdsa.PublicKey) *PublicKey {
pk := &PublicKey{
CreationTime: creationTime,
PubKeyAlgo: PubKeyAlgoECDSA,
PublicKey: pub,
ec: new(ecdsaKey),
}
switch pub.Curve {
case elliptic.P256():
pk.ec.oid = oidCurveP256
case elliptic.P384():
pk.ec.oid = oidCurveP384
case elliptic.P521():
pk.ec.oid = oidCurveP521
default:
panic("unknown elliptic curve")
}
pk.ec.p.bytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
// The bit length is 3 (for the 0x04 specifying an uncompressed key)
// plus two field elements (for x and y), which are rounded up to the
// nearest byte. See https://tools.ietf.org/html/rfc6637#section-6
fieldBytes := (pub.Curve.Params().BitSize + 7) & ^7
pk.ec.p.bitLength = uint16(3 + fieldBytes + fieldBytes)
pk.setFingerPrintAndKeyId()
return pk
}
func (pk *PublicKey) parse(r io.Reader) (err error) {
// RFC 4880, section 5.5.2
var buf [6]byte
_, err = readFull(r, buf[:])
if err != nil {
return
}
if buf[0] != 4 {
return errors.UnsupportedError("public key version")
}
pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5])
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
err = pk.parseRSA(r)
case PubKeyAlgoDSA:
err = pk.parseDSA(r)
case PubKeyAlgoElGamal:
err = pk.parseElGamal(r)
case PubKeyAlgoECDSA:
pk.ec = new(ecdsaKey)
if err = pk.ec.parse(r); err != nil {
return err
}
pk.PublicKey, err = pk.ec.newECDSA()
case PubKeyAlgoECDH:
pk.ec = new(ecdsaKey)
if err = pk.ec.parse(r); err != nil {
return
}
pk.ecdh = new(ecdhKdf)
if err = pk.ecdh.parse(r); err != nil {
return
}
// The ECDH key is stored in an ecdsa.PublicKey for convenience.
pk.PublicKey, err = pk.ec.newECDSA()
default:
err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
}
if err != nil {
return
}
pk.setFingerPrintAndKeyId()
return
}
func (pk *PublicKey) setFingerPrintAndKeyId() {
// RFC 4880, section 12.2
fingerPrint := sha1.New()
pk.SerializeSignaturePrefix(fingerPrint)
pk.serializeWithoutHeaders(fingerPrint)
copy(pk.Fingerprint[:], fingerPrint.Sum(nil))
pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20])
}
// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
// section 5.5.2.
func (pk *PublicKey) parseRSA(r io.Reader) (err error) {
pk.n.bytes, pk.n.bitLength, err = readMPI(r)
if err != nil {
return
}
pk.e.bytes, pk.e.bitLength, err = readMPI(r)
if err != nil {
return
}
if len(pk.e.bytes) > 3 {
err = errors.UnsupportedError("large public exponent")
return
}
rsa := &rsa.PublicKey{
N: new(big.Int).SetBytes(pk.n.bytes),
E: 0,
}
for i := 0; i < len(pk.e.bytes); i++ {
rsa.E <<= 8
rsa.E |= int(pk.e.bytes[i])
}
pk.PublicKey = rsa
return
}
// parseDSA parses DSA public key material from the given Reader. See RFC 4880,
// section 5.5.2.
func (pk *PublicKey) parseDSA(r io.Reader) (err error) {
pk.p.bytes, pk.p.bitLength, err = readMPI(r)
if err != nil {
return
}
pk.q.bytes, pk.q.bitLength, err = readMPI(r)
if err != nil {
return
}
pk.g.bytes, pk.g.bitLength, err = readMPI(r)
if err != nil {
return
}
pk.y.bytes, pk.y.bitLength, err = readMPI(r)
if err != nil {
return
}
dsa := new(dsa.PublicKey)
dsa.P = new(big.Int).SetBytes(pk.p.bytes)
dsa.Q = new(big.Int).SetBytes(pk.q.bytes)
dsa.G = new(big.Int).SetBytes(pk.g.bytes)
dsa.Y = new(big.Int).SetBytes(pk.y.bytes)
pk.PublicKey = dsa
return
}
// parseElGamal parses ElGamal public key material from the given Reader. See
// RFC 4880, section 5.5.2.
func (pk *PublicKey) parseElGamal(r io.Reader) (err error) {
pk.p.bytes, pk.p.bitLength, err = readMPI(r)
if err != nil {
return
}
pk.g.bytes, pk.g.bitLength, err = readMPI(r)
if err != nil {
return
}
pk.y.bytes, pk.y.bitLength, err = readMPI(r)
if err != nil {
return
}
elgamal := new(elgamal.PublicKey)
elgamal.P = new(big.Int).SetBytes(pk.p.bytes)
elgamal.G = new(big.Int).SetBytes(pk.g.bytes)
elgamal.Y = new(big.Int).SetBytes(pk.y.bytes)
pk.PublicKey = elgamal
return
}
// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
// The prefix is used when calculating a signature over this public key. See
// RFC 4880, section 5.2.4.
func (pk *PublicKey) SerializeSignaturePrefix(h io.Writer) {
var pLength uint16
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
pLength += 2 + uint16(len(pk.n.bytes))
pLength += 2 + uint16(len(pk.e.bytes))
case PubKeyAlgoDSA:
pLength += 2 + uint16(len(pk.p.bytes))
pLength += 2 + uint16(len(pk.q.bytes))
pLength += 2 + uint16(len(pk.g.bytes))
pLength += 2 + uint16(len(pk.y.bytes))
case PubKeyAlgoElGamal:
pLength += 2 + uint16(len(pk.p.bytes))
pLength += 2 + uint16(len(pk.g.bytes))
pLength += 2 + uint16(len(pk.y.bytes))
case PubKeyAlgoECDSA:
pLength += uint16(pk.ec.byteLen())
case PubKeyAlgoECDH:
pLength += uint16(pk.ec.byteLen())
pLength += uint16(pk.ecdh.byteLen())
default:
panic("unknown public key algorithm")
}
pLength += 6
h.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
return
}
func (pk *PublicKey) Serialize(w io.Writer) (err error) {
length := 6 // 6 byte header
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
length += 2 + len(pk.n.bytes)
length += 2 + len(pk.e.bytes)
case PubKeyAlgoDSA:
length += 2 + len(pk.p.bytes)
length += 2 + len(pk.q.bytes)
length += 2 + len(pk.g.bytes)
length += 2 + len(pk.y.bytes)
case PubKeyAlgoElGamal:
length += 2 + len(pk.p.bytes)
length += 2 + len(pk.g.bytes)
length += 2 + len(pk.y.bytes)
case PubKeyAlgoECDSA:
length += pk.ec.byteLen()
case PubKeyAlgoECDH:
length += pk.ec.byteLen()
length += pk.ecdh.byteLen()
default:
panic("unknown public key algorithm")
}
packetType := packetTypePublicKey
if pk.IsSubkey {
packetType = packetTypePublicSubkey
}
err = serializeHeader(w, packetType, length)
if err != nil {
return
}
return pk.serializeWithoutHeaders(w)
}
// serializeWithoutHeaders marshals the PublicKey to w in the form of an
// OpenPGP public key packet, not including the packet header.
func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
var buf [6]byte
buf[0] = 4
t := uint32(pk.CreationTime.Unix())
buf[1] = byte(t >> 24)
buf[2] = byte(t >> 16)
buf[3] = byte(t >> 8)
buf[4] = byte(t)
buf[5] = byte(pk.PubKeyAlgo)
_, err = w.Write(buf[:])
if err != nil {
return
}
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
return writeMPIs(w, pk.n, pk.e)
case PubKeyAlgoDSA:
return writeMPIs(w, pk.p, pk.q, pk.g, pk.y)
case PubKeyAlgoElGamal:
return writeMPIs(w, pk.p, pk.g, pk.y)
case PubKeyAlgoECDSA:
return pk.ec.serialize(w)
case PubKeyAlgoECDH:
if err = pk.ec.serialize(w); err != nil {
return
}
return pk.ecdh.serialize(w)
}
return errors.InvalidArgumentError("bad public-key algorithm")
}
// CanSign returns true iff this public key can generate signatures
func (pk *PublicKey) CanSign() bool {
return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly && pk.PubKeyAlgo != PubKeyAlgoElGamal
}
// VerifySignature returns nil iff sig is a valid signature, made by this
// public key, of the data hashed into signed. signed is mutated by this call.
func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) {
if !pk.CanSign() {
return errors.InvalidArgumentError("public key cannot generate signatures")
}
signed.Write(sig.HashSuffix)
hashBytes := signed.Sum(nil)
if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
return errors.SignatureError("hash tag doesn't match")
}
if pk.PubKeyAlgo != sig.PubKeyAlgo {
return errors.InvalidArgumentError("public key and signature use different algorithms")
}
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey)
err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, padToKeySize(rsaPublicKey, sig.RSASignature.bytes))
if err != nil {
return errors.SignatureError("RSA verification failure")
}
return nil
case PubKeyAlgoDSA:
dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
if len(hashBytes) > subgroupSize {
hashBytes = hashBytes[:subgroupSize]
}
if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
return errors.SignatureError("DSA verification failure")
}
return nil
case PubKeyAlgoECDSA:
ecdsaPublicKey := pk.PublicKey.(*ecdsa.PublicKey)
if !ecdsa.Verify(ecdsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.ECDSASigR.bytes), new(big.Int).SetBytes(sig.ECDSASigS.bytes)) {
return errors.SignatureError("ECDSA verification failure")
}
return nil
default:
return errors.SignatureError("Unsupported public key algorithm used in signature")
}
}
// VerifySignatureV3 returns nil iff sig is a valid signature, made by this
// public key, of the data hashed into signed. signed is mutated by this call.
func (pk *PublicKey) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) {
if !pk.CanSign() {
return errors.InvalidArgumentError("public key cannot generate signatures")
}
suffix := make([]byte, 5)
suffix[0] = byte(sig.SigType)
binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix()))
signed.Write(suffix)
hashBytes := signed.Sum(nil)
if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
return errors.SignatureError("hash tag doesn't match")
}
if pk.PubKeyAlgo != sig.PubKeyAlgo {
return errors.InvalidArgumentError("public key and signature use different algorithms")
}
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
rsaPublicKey := pk.PublicKey.(*rsa.PublicKey)
if err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, padToKeySize(rsaPublicKey, sig.RSASignature.bytes)); err != nil {
return errors.SignatureError("RSA verification failure")
}
return
case PubKeyAlgoDSA:
dsaPublicKey := pk.PublicKey.(*dsa.PublicKey)
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
if len(hashBytes) > subgroupSize {
hashBytes = hashBytes[:subgroupSize]
}
if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
return errors.SignatureError("DSA verification failure")
}
return nil
default:
panic("shouldn't happen")
}
}
// keySignatureHash returns a Hash of the message that needs to be signed for
// pk to assert a subkey relationship to signed.
func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
if !hashFunc.Available() {
return nil, errors.UnsupportedError("hash function")
}
h = hashFunc.New()
// RFC 4880, section 5.2.4
pk.SerializeSignaturePrefix(h)
pk.serializeWithoutHeaders(h)
signed.SerializeSignaturePrefix(h)
signed.serializeWithoutHeaders(h)
return
}
// VerifyKeySignature returns nil iff sig is a valid signature, made by this
// public key, of signed.
func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error {
h, err := keySignatureHash(pk, signed, sig.Hash)
if err != nil {
return err
}
if err = pk.VerifySignature(h, sig); err != nil {
return err
}
if sig.FlagSign {
// Signing subkeys must be cross-signed. See
// https://www.gnupg.org/faq/subkey-cross-certify.html.
if sig.EmbeddedSignature == nil {
return errors.StructuralError("signing subkey is missing cross-signature")
}
// Verify the cross-signature. This is calculated over the same
// data as the main signature, so we cannot just recursively
// call signed.VerifyKeySignature(...)
if h, err = keySignatureHash(pk, signed, sig.EmbeddedSignature.Hash); err != nil {
return errors.StructuralError("error while hashing for cross-signature: " + err.Error())
}
if err := signed.VerifySignature(h, sig.EmbeddedSignature); err != nil {
return errors.StructuralError("error while verifying cross-signature: " + err.Error())
}
}
return nil
}
func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
if !hashFunc.Available() {
return nil, errors.UnsupportedError("hash function")
}
h = hashFunc.New()
// RFC 4880, section 5.2.4
pk.SerializeSignaturePrefix(h)
pk.serializeWithoutHeaders(h)
return
}
// VerifyRevocationSignature returns nil iff sig is a valid signature, made by this
// public key.
func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) {
h, err := keyRevocationHash(pk, sig.Hash)
if err != nil {
return err
}
return pk.VerifySignature(h, sig)
}
// userIdSignatureHash returns a Hash of the message that needs to be signed
// to assert that pk is a valid key for id.
func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
if !hashFunc.Available() {
return nil, errors.UnsupportedError("hash function")
}
h = hashFunc.New()
// RFC 4880, section 5.2.4
pk.SerializeSignaturePrefix(h)
pk.serializeWithoutHeaders(h)
var buf [5]byte
buf[0] = 0xb4
buf[1] = byte(len(id) >> 24)
buf[2] = byte(len(id) >> 16)
buf[3] = byte(len(id) >> 8)
buf[4] = byte(len(id))
h.Write(buf[:])
h.Write([]byte(id))
return
}
// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this
// public key, that id is the identity of pub.
func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) {
h, err := userIdSignatureHash(id, pub, sig.Hash)
if err != nil {
return err
}
return pk.VerifySignature(h, sig)
}
// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this
// public key, that id is the identity of pub.
func (pk *PublicKey) VerifyUserIdSignatureV3(id string, pub *PublicKey, sig *SignatureV3) (err error) {
h, err := userIdSignatureV3Hash(id, pub, sig.Hash)
if err != nil {
return err
}
return pk.VerifySignatureV3(h, sig)
}
// KeyIdString returns the public key's fingerprint in capital hex
// (e.g. "6C7EE1B8621CC013").
func (pk *PublicKey) KeyIdString() string {
return fmt.Sprintf("%X", pk.Fingerprint[12:20])
}
// KeyIdShortString returns the short form of public key's fingerprint
// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
func (pk *PublicKey) KeyIdShortString() string {
return fmt.Sprintf("%X", pk.Fingerprint[16:20])
}
// A parsedMPI is used to store the contents of a big integer, along with the
// bit length that was specified in the original input. This allows the MPI to
// be reserialized exactly.
type parsedMPI struct {
bytes []byte
bitLength uint16
}
// writeMPIs is a utility function for serializing several big integers to the
// given Writer.
func writeMPIs(w io.Writer, mpis ...parsedMPI) (err error) {
for _, mpi := range mpis {
err = writeMPI(w, mpi.bitLength, mpi.bytes)
if err != nil {
return
}
}
return
}
// BitLength returns the bit length for the given public key.
func (pk *PublicKey) BitLength() (bitLength uint16, err error) {
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
bitLength = pk.n.bitLength
case PubKeyAlgoDSA:
bitLength = pk.p.bitLength
case PubKeyAlgoElGamal:
bitLength = pk.p.bitLength
default:
err = errors.InvalidArgumentError("bad public-key algorithm")
}
return
}

279
vendor/golang.org/x/crypto/openpgp/packet/public_key_v3.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,279 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"crypto"
"crypto/md5"
"crypto/rsa"
"encoding/binary"
"fmt"
"hash"
"io"
"math/big"
"strconv"
"time"
"golang.org/x/crypto/openpgp/errors"
)
// PublicKeyV3 represents older, version 3 public keys. These keys are less secure and
// should not be used for signing or encrypting. They are supported here only for
// parsing version 3 key material and validating signatures.
// See RFC 4880, section 5.5.2.
type PublicKeyV3 struct {
CreationTime time.Time
DaysToExpire uint16
PubKeyAlgo PublicKeyAlgorithm
PublicKey *rsa.PublicKey
Fingerprint [16]byte
KeyId uint64
IsSubkey bool
n, e parsedMPI
}
// newRSAPublicKeyV3 returns a PublicKey that wraps the given rsa.PublicKey.
// Included here for testing purposes only. RFC 4880, section 5.5.2:
// "an implementation MUST NOT generate a V3 key, but MAY accept it."
func newRSAPublicKeyV3(creationTime time.Time, pub *rsa.PublicKey) *PublicKeyV3 {
pk := &PublicKeyV3{
CreationTime: creationTime,
PublicKey: pub,
n: fromBig(pub.N),
e: fromBig(big.NewInt(int64(pub.E))),
}
pk.setFingerPrintAndKeyId()
return pk
}
func (pk *PublicKeyV3) parse(r io.Reader) (err error) {
// RFC 4880, section 5.5.2
var buf [8]byte
if _, err = readFull(r, buf[:]); err != nil {
return
}
if buf[0] < 2 || buf[0] > 3 {
return errors.UnsupportedError("public key version")
}
pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
pk.DaysToExpire = binary.BigEndian.Uint16(buf[5:7])
pk.PubKeyAlgo = PublicKeyAlgorithm(buf[7])
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
err = pk.parseRSA(r)
default:
err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
}
if err != nil {
return
}
pk.setFingerPrintAndKeyId()
return
}
func (pk *PublicKeyV3) setFingerPrintAndKeyId() {
// RFC 4880, section 12.2
fingerPrint := md5.New()
fingerPrint.Write(pk.n.bytes)
fingerPrint.Write(pk.e.bytes)
fingerPrint.Sum(pk.Fingerprint[:0])
pk.KeyId = binary.BigEndian.Uint64(pk.n.bytes[len(pk.n.bytes)-8:])
}
// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
// section 5.5.2.
func (pk *PublicKeyV3) parseRSA(r io.Reader) (err error) {
if pk.n.bytes, pk.n.bitLength, err = readMPI(r); err != nil {
return
}
if pk.e.bytes, pk.e.bitLength, err = readMPI(r); err != nil {
return
}
// RFC 4880 Section 12.2 requires the low 8 bytes of the
// modulus to form the key id.
if len(pk.n.bytes) < 8 {
return errors.StructuralError("v3 public key modulus is too short")
}
if len(pk.e.bytes) > 3 {
err = errors.UnsupportedError("large public exponent")
return
}
rsa := &rsa.PublicKey{N: new(big.Int).SetBytes(pk.n.bytes)}
for i := 0; i < len(pk.e.bytes); i++ {
rsa.E <<= 8
rsa.E |= int(pk.e.bytes[i])
}
pk.PublicKey = rsa
return
}
// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
// The prefix is used when calculating a signature over this public key. See
// RFC 4880, section 5.2.4.
func (pk *PublicKeyV3) SerializeSignaturePrefix(w io.Writer) {
var pLength uint16
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
pLength += 2 + uint16(len(pk.n.bytes))
pLength += 2 + uint16(len(pk.e.bytes))
default:
panic("unknown public key algorithm")
}
pLength += 6
w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
return
}
func (pk *PublicKeyV3) Serialize(w io.Writer) (err error) {
length := 8 // 8 byte header
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
length += 2 + len(pk.n.bytes)
length += 2 + len(pk.e.bytes)
default:
panic("unknown public key algorithm")
}
packetType := packetTypePublicKey
if pk.IsSubkey {
packetType = packetTypePublicSubkey
}
if err = serializeHeader(w, packetType, length); err != nil {
return
}
return pk.serializeWithoutHeaders(w)
}
// serializeWithoutHeaders marshals the PublicKey to w in the form of an
// OpenPGP public key packet, not including the packet header.
func (pk *PublicKeyV3) serializeWithoutHeaders(w io.Writer) (err error) {
var buf [8]byte
// Version 3
buf[0] = 3
// Creation time
t := uint32(pk.CreationTime.Unix())
buf[1] = byte(t >> 24)
buf[2] = byte(t >> 16)
buf[3] = byte(t >> 8)
buf[4] = byte(t)
// Days to expire
buf[5] = byte(pk.DaysToExpire >> 8)
buf[6] = byte(pk.DaysToExpire)
// Public key algorithm
buf[7] = byte(pk.PubKeyAlgo)
if _, err = w.Write(buf[:]); err != nil {
return
}
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
return writeMPIs(w, pk.n, pk.e)
}
return errors.InvalidArgumentError("bad public-key algorithm")
}
// CanSign returns true iff this public key can generate signatures
func (pk *PublicKeyV3) CanSign() bool {
return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly
}
// VerifySignatureV3 returns nil iff sig is a valid signature, made by this
// public key, of the data hashed into signed. signed is mutated by this call.
func (pk *PublicKeyV3) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) {
if !pk.CanSign() {
return errors.InvalidArgumentError("public key cannot generate signatures")
}
suffix := make([]byte, 5)
suffix[0] = byte(sig.SigType)
binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix()))
signed.Write(suffix)
hashBytes := signed.Sum(nil)
if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
return errors.SignatureError("hash tag doesn't match")
}
if pk.PubKeyAlgo != sig.PubKeyAlgo {
return errors.InvalidArgumentError("public key and signature use different algorithms")
}
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
if err = rsa.VerifyPKCS1v15(pk.PublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes); err != nil {
return errors.SignatureError("RSA verification failure")
}
return
default:
// V3 public keys only support RSA.
panic("shouldn't happen")
}
}
// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this
// public key, that id is the identity of pub.
func (pk *PublicKeyV3) VerifyUserIdSignatureV3(id string, pub *PublicKeyV3, sig *SignatureV3) (err error) {
h, err := userIdSignatureV3Hash(id, pk, sig.Hash)
if err != nil {
return err
}
return pk.VerifySignatureV3(h, sig)
}
// VerifyKeySignatureV3 returns nil iff sig is a valid signature, made by this
// public key, of signed.
func (pk *PublicKeyV3) VerifyKeySignatureV3(signed *PublicKeyV3, sig *SignatureV3) (err error) {
h, err := keySignatureHash(pk, signed, sig.Hash)
if err != nil {
return err
}
return pk.VerifySignatureV3(h, sig)
}
// userIdSignatureV3Hash returns a Hash of the message that needs to be signed
// to assert that pk is a valid key for id.
func userIdSignatureV3Hash(id string, pk signingKey, hfn crypto.Hash) (h hash.Hash, err error) {
if !hfn.Available() {
return nil, errors.UnsupportedError("hash function")
}
h = hfn.New()
// RFC 4880, section 5.2.4
pk.SerializeSignaturePrefix(h)
pk.serializeWithoutHeaders(h)
h.Write([]byte(id))
return
}
// KeyIdString returns the public key's fingerprint in capital hex
// (e.g. "6C7EE1B8621CC013").
func (pk *PublicKeyV3) KeyIdString() string {
return fmt.Sprintf("%X", pk.KeyId)
}
// KeyIdShortString returns the short form of public key's fingerprint
// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
func (pk *PublicKeyV3) KeyIdShortString() string {
return fmt.Sprintf("%X", pk.KeyId&0xFFFFFFFF)
}
// BitLength returns the bit length for the given public key.
func (pk *PublicKeyV3) BitLength() (bitLength uint16, err error) {
switch pk.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
bitLength = pk.n.bitLength
default:
err = errors.InvalidArgumentError("bad public-key algorithm")
}
return
}

76
vendor/golang.org/x/crypto/openpgp/packet/reader.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,76 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"golang.org/x/crypto/openpgp/errors"
"io"
)
// Reader reads packets from an io.Reader and allows packets to be 'unread' so
// that they result from the next call to Next.
type Reader struct {
q []Packet
readers []io.Reader
}
// New io.Readers are pushed when a compressed or encrypted packet is processed
// and recursively treated as a new source of packets. However, a carefully
// crafted packet can trigger an infinite recursive sequence of packets. See
// http://mumble.net/~campbell/misc/pgp-quine
// https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-4402
// This constant limits the number of recursive packets that may be pushed.
const maxReaders = 32
// Next returns the most recently unread Packet, or reads another packet from
// the top-most io.Reader. Unknown packet types are skipped.
func (r *Reader) Next() (p Packet, err error) {
if len(r.q) > 0 {
p = r.q[len(r.q)-1]
r.q = r.q[:len(r.q)-1]
return
}
for len(r.readers) > 0 {
p, err = Read(r.readers[len(r.readers)-1])
if err == nil {
return
}
if err == io.EOF {
r.readers = r.readers[:len(r.readers)-1]
continue
}
if _, ok := err.(errors.UnknownPacketTypeError); !ok {
return nil, err
}
}
return nil, io.EOF
}
// Push causes the Reader to start reading from a new io.Reader. When an EOF
// error is seen from the new io.Reader, it is popped and the Reader continues
// to read from the next most recent io.Reader. Push returns a StructuralError
// if pushing the reader would exceed the maximum recursion level, otherwise it
// returns nil.
func (r *Reader) Push(reader io.Reader) (err error) {
if len(r.readers) >= maxReaders {
return errors.StructuralError("too many layers of packets")
}
r.readers = append(r.readers, reader)
return nil
}
// Unread causes the given Packet to be returned from the next call to Next.
func (r *Reader) Unread(p Packet) {
r.q = append(r.q, p)
}
func NewReader(r io.Reader) *Reader {
return &Reader{
q: nil,
readers: []io.Reader{r},
}
}

731
vendor/golang.org/x/crypto/openpgp/packet/signature.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,731 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"bytes"
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"encoding/asn1"
"encoding/binary"
"hash"
"io"
"math/big"
"strconv"
"time"
"golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/s2k"
)
const (
// See RFC 4880, section 5.2.3.21 for details.
KeyFlagCertify = 1 << iota
KeyFlagSign
KeyFlagEncryptCommunications
KeyFlagEncryptStorage
)
// Signature represents a signature. See RFC 4880, section 5.2.
type Signature struct {
SigType SignatureType
PubKeyAlgo PublicKeyAlgorithm
Hash crypto.Hash
// HashSuffix is extra data that is hashed in after the signed data.
HashSuffix []byte
// HashTag contains the first two bytes of the hash for fast rejection
// of bad signed data.
HashTag [2]byte
CreationTime time.Time
RSASignature parsedMPI
DSASigR, DSASigS parsedMPI
ECDSASigR, ECDSASigS parsedMPI
// rawSubpackets contains the unparsed subpackets, in order.
rawSubpackets []outputSubpacket
// The following are optional so are nil when not included in the
// signature.
SigLifetimeSecs, KeyLifetimeSecs *uint32
PreferredSymmetric, PreferredHash, PreferredCompression []uint8
IssuerKeyId *uint64
IsPrimaryId *bool
// FlagsValid is set if any flags were given. See RFC 4880, section
// 5.2.3.21 for details.
FlagsValid bool
FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage bool
// RevocationReason is set if this signature has been revoked.
// See RFC 4880, section 5.2.3.23 for details.
RevocationReason *uint8
RevocationReasonText string
// MDC is set if this signature has a feature packet that indicates
// support for MDC subpackets.
MDC bool
// EmbeddedSignature, if non-nil, is a signature of the parent key, by
// this key. This prevents an attacker from claiming another's signing
// subkey as their own.
EmbeddedSignature *Signature
outSubpackets []outputSubpacket
}
func (sig *Signature) parse(r io.Reader) (err error) {
// RFC 4880, section 5.2.3
var buf [5]byte
_, err = readFull(r, buf[:1])
if err != nil {
return
}
if buf[0] != 4 {
err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
return
}
_, err = readFull(r, buf[:5])
if err != nil {
return
}
sig.SigType = SignatureType(buf[0])
sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1])
switch sig.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA:
default:
err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
return
}
var ok bool
sig.Hash, ok = s2k.HashIdToHash(buf[2])
if !ok {
return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
}
hashedSubpacketsLength := int(buf[3])<<8 | int(buf[4])
l := 6 + hashedSubpacketsLength
sig.HashSuffix = make([]byte, l+6)
sig.HashSuffix[0] = 4
copy(sig.HashSuffix[1:], buf[:5])
hashedSubpackets := sig.HashSuffix[6:l]
_, err = readFull(r, hashedSubpackets)
if err != nil {
return
}
// See RFC 4880, section 5.2.4
trailer := sig.HashSuffix[l:]
trailer[0] = 4
trailer[1] = 0xff
trailer[2] = uint8(l >> 24)
trailer[3] = uint8(l >> 16)
trailer[4] = uint8(l >> 8)
trailer[5] = uint8(l)
err = parseSignatureSubpackets(sig, hashedSubpackets, true)
if err != nil {
return
}
_, err = readFull(r, buf[:2])
if err != nil {
return
}
unhashedSubpacketsLength := int(buf[0])<<8 | int(buf[1])
unhashedSubpackets := make([]byte, unhashedSubpacketsLength)
_, err = readFull(r, unhashedSubpackets)
if err != nil {
return
}
err = parseSignatureSubpackets(sig, unhashedSubpackets, false)
if err != nil {
return
}
_, err = readFull(r, sig.HashTag[:2])
if err != nil {
return
}
switch sig.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r)
case PubKeyAlgoDSA:
sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r)
if err == nil {
sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
}
case PubKeyAlgoECDSA:
sig.ECDSASigR.bytes, sig.ECDSASigR.bitLength, err = readMPI(r)
if err == nil {
sig.ECDSASigS.bytes, sig.ECDSASigS.bitLength, err = readMPI(r)
}
default:
panic("unreachable")
}
return
}
// parseSignatureSubpackets parses subpackets of the main signature packet. See
// RFC 4880, section 5.2.3.1.
func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) (err error) {
for len(subpackets) > 0 {
subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed)
if err != nil {
return
}
}
if sig.CreationTime.IsZero() {
err = errors.StructuralError("no creation time in signature")
}
return
}
type signatureSubpacketType uint8
const (
creationTimeSubpacket signatureSubpacketType = 2
signatureExpirationSubpacket signatureSubpacketType = 3
keyExpirationSubpacket signatureSubpacketType = 9
prefSymmetricAlgosSubpacket signatureSubpacketType = 11
issuerSubpacket signatureSubpacketType = 16
prefHashAlgosSubpacket signatureSubpacketType = 21
prefCompressionSubpacket signatureSubpacketType = 22
primaryUserIdSubpacket signatureSubpacketType = 25
keyFlagsSubpacket signatureSubpacketType = 27
reasonForRevocationSubpacket signatureSubpacketType = 29
featuresSubpacket signatureSubpacketType = 30
embeddedSignatureSubpacket signatureSubpacketType = 32
)
// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1.
func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (rest []byte, err error) {
// RFC 4880, section 5.2.3.1
var (
length uint32
packetType signatureSubpacketType
isCritical bool
)
switch {
case subpacket[0] < 192:
length = uint32(subpacket[0])
subpacket = subpacket[1:]
case subpacket[0] < 255:
if len(subpacket) < 2 {
goto Truncated
}
length = uint32(subpacket[0]-192)<<8 + uint32(subpacket[1]) + 192
subpacket = subpacket[2:]
default:
if len(subpacket) < 5 {
goto Truncated
}
length = uint32(subpacket[1])<<24 |
uint32(subpacket[2])<<16 |
uint32(subpacket[3])<<8 |
uint32(subpacket[4])
subpacket = subpacket[5:]
}
if length > uint32(len(subpacket)) {
goto Truncated
}
rest = subpacket[length:]
subpacket = subpacket[:length]
if len(subpacket) == 0 {
err = errors.StructuralError("zero length signature subpacket")
return
}
packetType = signatureSubpacketType(subpacket[0] & 0x7f)
isCritical = subpacket[0]&0x80 == 0x80
subpacket = subpacket[1:]
sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket})
switch packetType {
case creationTimeSubpacket:
if !isHashed {
err = errors.StructuralError("signature creation time in non-hashed area")
return
}
if len(subpacket) != 4 {
err = errors.StructuralError("signature creation time not four bytes")
return
}
t := binary.BigEndian.Uint32(subpacket)
sig.CreationTime = time.Unix(int64(t), 0)
case signatureExpirationSubpacket:
// Signature expiration time, section 5.2.3.10
if !isHashed {
return
}
if len(subpacket) != 4 {
err = errors.StructuralError("expiration subpacket with bad length")
return
}
sig.SigLifetimeSecs = new(uint32)
*sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
case keyExpirationSubpacket:
// Key expiration time, section 5.2.3.6
if !isHashed {
return
}
if len(subpacket) != 4 {
err = errors.StructuralError("key expiration subpacket with bad length")
return
}
sig.KeyLifetimeSecs = new(uint32)
*sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket)
case prefSymmetricAlgosSubpacket:
// Preferred symmetric algorithms, section 5.2.3.7
if !isHashed {
return
}
sig.PreferredSymmetric = make([]byte, len(subpacket))
copy(sig.PreferredSymmetric, subpacket)
case issuerSubpacket:
// Issuer, section 5.2.3.5
if len(subpacket) != 8 {
err = errors.StructuralError("issuer subpacket with bad length")
return
}
sig.IssuerKeyId = new(uint64)
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket)
case prefHashAlgosSubpacket:
// Preferred hash algorithms, section 5.2.3.8
if !isHashed {
return
}
sig.PreferredHash = make([]byte, len(subpacket))
copy(sig.PreferredHash, subpacket)
case prefCompressionSubpacket:
// Preferred compression algorithms, section 5.2.3.9
if !isHashed {
return
}
sig.PreferredCompression = make([]byte, len(subpacket))
copy(sig.PreferredCompression, subpacket)
case primaryUserIdSubpacket:
// Primary User ID, section 5.2.3.19
if !isHashed {
return
}
if len(subpacket) != 1 {
err = errors.StructuralError("primary user id subpacket with bad length")
return
}
sig.IsPrimaryId = new(bool)
if subpacket[0] > 0 {
*sig.IsPrimaryId = true
}
case keyFlagsSubpacket:
// Key flags, section 5.2.3.21
if !isHashed {
return
}
if len(subpacket) == 0 {
err = errors.StructuralError("empty key flags subpacket")
return
}
sig.FlagsValid = true
if subpacket[0]&KeyFlagCertify != 0 {
sig.FlagCertify = true
}
if subpacket[0]&KeyFlagSign != 0 {
sig.FlagSign = true
}
if subpacket[0]&KeyFlagEncryptCommunications != 0 {
sig.FlagEncryptCommunications = true
}
if subpacket[0]&KeyFlagEncryptStorage != 0 {
sig.FlagEncryptStorage = true
}
case reasonForRevocationSubpacket:
// Reason For Revocation, section 5.2.3.23
if !isHashed {
return
}
if len(subpacket) == 0 {
err = errors.StructuralError("empty revocation reason subpacket")
return
}
sig.RevocationReason = new(uint8)
*sig.RevocationReason = subpacket[0]
sig.RevocationReasonText = string(subpacket[1:])
case featuresSubpacket:
// Features subpacket, section 5.2.3.24 specifies a very general
// mechanism for OpenPGP implementations to signal support for new
// features. In practice, the subpacket is used exclusively to
// indicate support for MDC-protected encryption.
sig.MDC = len(subpacket) >= 1 && subpacket[0]&1 == 1
case embeddedSignatureSubpacket:
// Only usage is in signatures that cross-certify
// signing subkeys. section 5.2.3.26 describes the
// format, with its usage described in section 11.1
if sig.EmbeddedSignature != nil {
err = errors.StructuralError("Cannot have multiple embedded signatures")
return
}
sig.EmbeddedSignature = new(Signature)
// Embedded signatures are required to be v4 signatures see
// section 12.1. However, we only parse v4 signatures in this
// file anyway.
if err := sig.EmbeddedSignature.parse(bytes.NewBuffer(subpacket)); err != nil {
return nil, err
}
if sigType := sig.EmbeddedSignature.SigType; sigType != SigTypePrimaryKeyBinding {
return nil, errors.StructuralError("cross-signature has unexpected type " + strconv.Itoa(int(sigType)))
}
default:
if isCritical {
err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
return
}
}
return
Truncated:
err = errors.StructuralError("signature subpacket truncated")
return
}
// subpacketLengthLength returns the length, in bytes, of an encoded length value.
func subpacketLengthLength(length int) int {
if length < 192 {
return 1
}
if length < 16320 {
return 2
}
return 5
}
// serializeSubpacketLength marshals the given length into to.
func serializeSubpacketLength(to []byte, length int) int {
// RFC 4880, Section 4.2.2.
if length < 192 {
to[0] = byte(length)
return 1
}
if length < 16320 {
length -= 192
to[0] = byte((length >> 8) + 192)
to[1] = byte(length)
return 2
}
to[0] = 255
to[1] = byte(length >> 24)
to[2] = byte(length >> 16)
to[3] = byte(length >> 8)
to[4] = byte(length)
return 5
}
// subpacketsLength returns the serialized length, in bytes, of the given
// subpackets.
func subpacketsLength(subpackets []outputSubpacket, hashed bool) (length int) {
for _, subpacket := range subpackets {
if subpacket.hashed == hashed {
length += subpacketLengthLength(len(subpacket.contents) + 1)
length += 1 // type byte
length += len(subpacket.contents)
}
}
return
}
// serializeSubpackets marshals the given subpackets into to.
func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) {
for _, subpacket := range subpackets {
if subpacket.hashed == hashed {
n := serializeSubpacketLength(to, len(subpacket.contents)+1)
to[n] = byte(subpacket.subpacketType)
to = to[1+n:]
n = copy(to, subpacket.contents)
to = to[n:]
}
}
return
}
// KeyExpired returns whether sig is a self-signature of a key that has
// expired.
func (sig *Signature) KeyExpired(currentTime time.Time) bool {
if sig.KeyLifetimeSecs == nil {
return false
}
expiry := sig.CreationTime.Add(time.Duration(*sig.KeyLifetimeSecs) * time.Second)
return currentTime.After(expiry)
}
// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing.
func (sig *Signature) buildHashSuffix() (err error) {
hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true)
var ok bool
l := 6 + hashedSubpacketsLen
sig.HashSuffix = make([]byte, l+6)
sig.HashSuffix[0] = 4
sig.HashSuffix[1] = uint8(sig.SigType)
sig.HashSuffix[2] = uint8(sig.PubKeyAlgo)
sig.HashSuffix[3], ok = s2k.HashToHashId(sig.Hash)
if !ok {
sig.HashSuffix = nil
return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
}
sig.HashSuffix[4] = byte(hashedSubpacketsLen >> 8)
sig.HashSuffix[5] = byte(hashedSubpacketsLen)
serializeSubpackets(sig.HashSuffix[6:l], sig.outSubpackets, true)
trailer := sig.HashSuffix[l:]
trailer[0] = 4
trailer[1] = 0xff
trailer[2] = byte(l >> 24)
trailer[3] = byte(l >> 16)
trailer[4] = byte(l >> 8)
trailer[5] = byte(l)
return
}
func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) {
err = sig.buildHashSuffix()
if err != nil {
return
}
h.Write(sig.HashSuffix)
digest = h.Sum(nil)
copy(sig.HashTag[:], digest)
return
}
// Sign signs a message with a private key. The hash, h, must contain
// the hash of the message to be signed and will be mutated by this function.
// On success, the signature is stored in sig. Call Serialize to write it out.
// If config is nil, sensible defaults will be used.
func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err error) {
sig.outSubpackets = sig.buildSubpackets()
digest, err := sig.signPrepareHash(h)
if err != nil {
return
}
switch priv.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
// supports both *rsa.PrivateKey and crypto.Signer
sig.RSASignature.bytes, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash)
sig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes))
case PubKeyAlgoDSA:
dsaPriv := priv.PrivateKey.(*dsa.PrivateKey)
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8
if len(digest) > subgroupSize {
digest = digest[:subgroupSize]
}
r, s, err := dsa.Sign(config.Random(), dsaPriv, digest)
if err == nil {
sig.DSASigR.bytes = r.Bytes()
sig.DSASigR.bitLength = uint16(8 * len(sig.DSASigR.bytes))
sig.DSASigS.bytes = s.Bytes()
sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes))
}
case PubKeyAlgoECDSA:
var r, s *big.Int
if pk, ok := priv.PrivateKey.(*ecdsa.PrivateKey); ok {
// direct support, avoid asn1 wrapping/unwrapping
r, s, err = ecdsa.Sign(config.Random(), pk, digest)
} else {
var b []byte
b, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash)
if err == nil {
r, s, err = unwrapECDSASig(b)
}
}
if err == nil {
sig.ECDSASigR = fromBig(r)
sig.ECDSASigS = fromBig(s)
}
default:
err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
}
return
}
// unwrapECDSASig parses the two integer components of an ASN.1-encoded ECDSA
// signature.
func unwrapECDSASig(b []byte) (r, s *big.Int, err error) {
var ecsdaSig struct {
R, S *big.Int
}
_, err = asn1.Unmarshal(b, &ecsdaSig)
if err != nil {
return
}
return ecsdaSig.R, ecsdaSig.S, nil
}
// SignUserId computes a signature from priv, asserting that pub is a valid
// key for the identity id. On success, the signature is stored in sig. Call
// Serialize to write it out.
// If config is nil, sensible defaults will be used.
func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, config *Config) error {
h, err := userIdSignatureHash(id, pub, sig.Hash)
if err != nil {
return err
}
return sig.Sign(h, priv, config)
}
// SignKey computes a signature from priv, asserting that pub is a subkey. On
// success, the signature is stored in sig. Call Serialize to write it out.
// If config is nil, sensible defaults will be used.
func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) error {
h, err := keySignatureHash(&priv.PublicKey, pub, sig.Hash)
if err != nil {
return err
}
return sig.Sign(h, priv, config)
}
// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been
// called first.
func (sig *Signature) Serialize(w io.Writer) (err error) {
if len(sig.outSubpackets) == 0 {
sig.outSubpackets = sig.rawSubpackets
}
if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil && sig.ECDSASigR.bytes == nil {
return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize")
}
sigLength := 0
switch sig.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
sigLength = 2 + len(sig.RSASignature.bytes)
case PubKeyAlgoDSA:
sigLength = 2 + len(sig.DSASigR.bytes)
sigLength += 2 + len(sig.DSASigS.bytes)
case PubKeyAlgoECDSA:
sigLength = 2 + len(sig.ECDSASigR.bytes)
sigLength += 2 + len(sig.ECDSASigS.bytes)
default:
panic("impossible")
}
unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false)
length := len(sig.HashSuffix) - 6 /* trailer not included */ +
2 /* length of unhashed subpackets */ + unhashedSubpacketsLen +
2 /* hash tag */ + sigLength
err = serializeHeader(w, packetTypeSignature, length)
if err != nil {
return
}
_, err = w.Write(sig.HashSuffix[:len(sig.HashSuffix)-6])
if err != nil {
return
}
unhashedSubpackets := make([]byte, 2+unhashedSubpacketsLen)
unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8)
unhashedSubpackets[1] = byte(unhashedSubpacketsLen)
serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false)
_, err = w.Write(unhashedSubpackets)
if err != nil {
return
}
_, err = w.Write(sig.HashTag[:])
if err != nil {
return
}
switch sig.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
err = writeMPIs(w, sig.RSASignature)
case PubKeyAlgoDSA:
err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
case PubKeyAlgoECDSA:
err = writeMPIs(w, sig.ECDSASigR, sig.ECDSASigS)
default:
panic("impossible")
}
return
}
// outputSubpacket represents a subpacket to be marshaled.
type outputSubpacket struct {
hashed bool // true if this subpacket is in the hashed area.
subpacketType signatureSubpacketType
isCritical bool
contents []byte
}
func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) {
creationTime := make([]byte, 4)
binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix()))
subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, false, creationTime})
if sig.IssuerKeyId != nil {
keyId := make([]byte, 8)
binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId)
subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
}
if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
sigLifetime := make([]byte, 4)
binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs)
subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime})
}
// Key flags may only appear in self-signatures or certification signatures.
if sig.FlagsValid {
var flags byte
if sig.FlagCertify {
flags |= KeyFlagCertify
}
if sig.FlagSign {
flags |= KeyFlagSign
}
if sig.FlagEncryptCommunications {
flags |= KeyFlagEncryptCommunications
}
if sig.FlagEncryptStorage {
flags |= KeyFlagEncryptStorage
}
subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
}
// The following subpackets may only appear in self-signatures
if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
keyLifetime := make([]byte, 4)
binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime})
}
if sig.IsPrimaryId != nil && *sig.IsPrimaryId {
subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}})
}
if len(sig.PreferredSymmetric) > 0 {
subpackets = append(subpackets, outputSubpacket{true, prefSymmetricAlgosSubpacket, false, sig.PreferredSymmetric})
}
if len(sig.PreferredHash) > 0 {
subpackets = append(subpackets, outputSubpacket{true, prefHashAlgosSubpacket, false, sig.PreferredHash})
}
if len(sig.PreferredCompression) > 0 {
subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression})
}
return
}

146
vendor/golang.org/x/crypto/openpgp/packet/signature_v3.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,146 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"crypto"
"encoding/binary"
"fmt"
"io"
"strconv"
"time"
"golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/s2k"
)
// SignatureV3 represents older version 3 signatures. These signatures are less secure
// than version 4 and should not be used to create new signatures. They are included
// here for backwards compatibility to read and validate with older key material.
// See RFC 4880, section 5.2.2.
type SignatureV3 struct {
SigType SignatureType
CreationTime time.Time
IssuerKeyId uint64
PubKeyAlgo PublicKeyAlgorithm
Hash crypto.Hash
HashTag [2]byte
RSASignature parsedMPI
DSASigR, DSASigS parsedMPI
}
func (sig *SignatureV3) parse(r io.Reader) (err error) {
// RFC 4880, section 5.2.2
var buf [8]byte
if _, err = readFull(r, buf[:1]); err != nil {
return
}
if buf[0] < 2 || buf[0] > 3 {
err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
return
}
if _, err = readFull(r, buf[:1]); err != nil {
return
}
if buf[0] != 5 {
err = errors.UnsupportedError(
"invalid hashed material length " + strconv.Itoa(int(buf[0])))
return
}
// Read hashed material: signature type + creation time
if _, err = readFull(r, buf[:5]); err != nil {
return
}
sig.SigType = SignatureType(buf[0])
t := binary.BigEndian.Uint32(buf[1:5])
sig.CreationTime = time.Unix(int64(t), 0)
// Eight-octet Key ID of signer.
if _, err = readFull(r, buf[:8]); err != nil {
return
}
sig.IssuerKeyId = binary.BigEndian.Uint64(buf[:])
// Public-key and hash algorithm
if _, err = readFull(r, buf[:2]); err != nil {
return
}
sig.PubKeyAlgo = PublicKeyAlgorithm(buf[0])
switch sig.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
default:
err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
return
}
var ok bool
if sig.Hash, ok = s2k.HashIdToHash(buf[1]); !ok {
return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
}
// Two-octet field holding left 16 bits of signed hash value.
if _, err = readFull(r, sig.HashTag[:2]); err != nil {
return
}
switch sig.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r)
case PubKeyAlgoDSA:
if sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r); err != nil {
return
}
sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
default:
panic("unreachable")
}
return
}
// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been
// called first.
func (sig *SignatureV3) Serialize(w io.Writer) (err error) {
buf := make([]byte, 8)
// Write the sig type and creation time
buf[0] = byte(sig.SigType)
binary.BigEndian.PutUint32(buf[1:5], uint32(sig.CreationTime.Unix()))
if _, err = w.Write(buf[:5]); err != nil {
return
}
// Write the issuer long key ID
binary.BigEndian.PutUint64(buf[:8], sig.IssuerKeyId)
if _, err = w.Write(buf[:8]); err != nil {
return
}
// Write public key algorithm, hash ID, and hash value
buf[0] = byte(sig.PubKeyAlgo)
hashId, ok := s2k.HashToHashId(sig.Hash)
if !ok {
return errors.UnsupportedError(fmt.Sprintf("hash function %v", sig.Hash))
}
buf[1] = hashId
copy(buf[2:4], sig.HashTag[:])
if _, err = w.Write(buf[:4]); err != nil {
return
}
if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil {
return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize")
}
switch sig.PubKeyAlgo {
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
err = writeMPIs(w, sig.RSASignature)
case PubKeyAlgoDSA:
err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
default:
panic("impossible")
}
return
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше