summaryrefslogtreecommitdiffstats
path: root/models/migrations/v1_19/v233.go
blob: ba4cd8e20b9959cde57318269b257b27c803b32c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_19 //nolint

import (
	"fmt"

	"code.gitea.io/gitea/modules/json"
	"code.gitea.io/gitea/modules/secret"
	"code.gitea.io/gitea/modules/setting"
	api "code.gitea.io/gitea/modules/structs"

	"xorm.io/builder"
	"xorm.io/xorm"
)

func batchProcess[T any](x *xorm.Engine, buf []T, query func(limit, start int) *xorm.Session, process func(*xorm.Session, T) error) error {
	size := cap(buf)
	start := 0
	for {
		err := query(size, start).Find(&buf)
		if err != nil {
			return err
		}
		if len(buf) == 0 {
			return nil
		}

		err = func() error {
			sess := x.NewSession()
			defer sess.Close()
			if err := sess.Begin(); err != nil {
				return fmt.Errorf("unable to allow start session. Error: %w", err)
			}
			for _, record := range buf {
				if err := process(sess, record); err != nil {
					return err
				}
			}
			return sess.Commit()
		}()
		if err != nil {
			return err
		}

		if len(buf) < size {
			return nil
		}
		start += size
		buf = buf[:0]
	}
}

func AddHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error {
	// Add the column to the table
	type Webhook struct {
		ID   int64  `xorm:"pk autoincr"`
		Type string `xorm:"VARCHAR(16) 'type'"`
		Meta string `xorm:"TEXT"` // store hook-specific attributes

		// HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
		HeaderAuthorizationEncrypted string `xorm:"TEXT"`
	}
	err := x.Sync(new(Webhook))
	if err != nil {
		return err
	}

	// Migrate the matrix webhooks

	type MatrixMeta struct {
		HomeserverURL string `json:"homeserver_url"`
		Room          string `json:"room_id"`
		MessageType   int    `json:"message_type"`
	}
	type MatrixMetaWithAccessToken struct {
		MatrixMeta
		AccessToken string `json:"access_token"`
	}

	err = batchProcess(x,
		make([]*Webhook, 0, 50),
		func(limit, start int) *xorm.Session {
			return x.Where("type=?", "matrix").OrderBy("id").Limit(limit, start)
		},
		func(sess *xorm.Session, hook *Webhook) error {
			// retrieve token from meta
			var withToken MatrixMetaWithAccessToken
			err := json.Unmarshal([]byte(hook.Meta), &withToken)
			if err != nil {
				return fmt.Errorf("unable to unmarshal matrix meta for webhook[id=%d]: %w", hook.ID, err)
			}
			if withToken.AccessToken == "" {
				return nil
			}

			// encrypt token
			authorization := "Bearer " + withToken.AccessToken
			hook.HeaderAuthorizationEncrypted, err = secret.EncryptSecret(setting.SecretKey, authorization)
			if err != nil {
				return fmt.Errorf("unable to encrypt access token for webhook[id=%d]: %w", hook.ID, err)
			}

			// remove token from meta
			withoutToken, err := json.Marshal(withToken.MatrixMeta)
			if err != nil {
				return fmt.Errorf("unable to marshal matrix meta for webhook[id=%d]: %w", hook.ID, err)
			}
			hook.Meta = string(withoutToken)

			// save in database
			count, err := sess.ID(hook.ID).Cols("meta", "header_authorization_encrypted").Update(hook)
			if count != 1 || err != nil {
				return fmt.Errorf("unable to update header_authorization_encrypted for webhook[id=%d]: %d,%w", hook.ID, count, err)
			}
			return nil
		})
	if err != nil {
		return err
	}

	// Remove access_token from HookTask

	type HookTask struct {
		ID             int64 `xorm:"pk autoincr"`
		HookID         int64
		PayloadContent string `xorm:"LONGTEXT"`
	}

	type MatrixPayloadSafe struct {
		Body          string               `json:"body"`
		MsgType       string               `json:"msgtype"`
		Format        string               `json:"format"`
		FormattedBody string               `json:"formatted_body"`
		Commits       []*api.PayloadCommit `json:"io.gitea.commits,omitempty"`
	}
	type MatrixPayloadUnsafe struct {
		MatrixPayloadSafe
		AccessToken string `json:"access_token"`
	}

	err = batchProcess(x,
		make([]*HookTask, 0, 50),
		func(limit, start int) *xorm.Session {
			return x.Where(builder.And(
				builder.In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"type": "matrix"})),
				builder.Like{"payload_content", "access_token"},
			)).OrderBy("id").Limit(limit, 0) // ignore the provided "start", since other payload were already converted and don't contain 'payload_content' anymore
		},
		func(sess *xorm.Session, hookTask *HookTask) error {
			// retrieve token from payload_content
			var withToken MatrixPayloadUnsafe
			err := json.Unmarshal([]byte(hookTask.PayloadContent), &withToken)
			if err != nil {
				return fmt.Errorf("unable to unmarshal payload_content for hook_task[id=%d]: %w", hookTask.ID, err)
			}
			if withToken.AccessToken == "" {
				return nil
			}

			// remove token from payload_content
			withoutToken, err := json.Marshal(withToken.MatrixPayloadSafe)
			if err != nil {
				return fmt.Errorf("unable to marshal payload_content for hook_task[id=%d]: %w", hookTask.ID, err)
			}
			hookTask.PayloadContent = string(withoutToken)

			// save in database
			count, err := sess.ID(hookTask.ID).Cols("payload_content").Update(hookTask)
			if count != 1 || err != nil {
				return fmt.Errorf("unable to update payload_content for hook_task[id=%d]: %d,%w", hookTask.ID, count, err)
			}
			return nil
		})
	if err != nil {
		return err
	}

	return nil
}