code.gitea.io/gitea@v1.21.7/models/migrations/v1_19/v233.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package v1_19 //nolint
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"code.gitea.io/gitea/modules/json"
    10  	"code.gitea.io/gitea/modules/secret"
    11  	"code.gitea.io/gitea/modules/setting"
    12  	api "code.gitea.io/gitea/modules/structs"
    13  
    14  	"xorm.io/builder"
    15  	"xorm.io/xorm"
    16  )
    17  
    18  func batchProcess[T any](x *xorm.Engine, buf []T, query func(limit, start int) *xorm.Session, process func(*xorm.Session, T) error) error {
    19  	size := cap(buf)
    20  	start := 0
    21  	for {
    22  		err := query(size, start).Find(&buf)
    23  		if err != nil {
    24  			return err
    25  		}
    26  		if len(buf) == 0 {
    27  			return nil
    28  		}
    29  
    30  		err = func() error {
    31  			sess := x.NewSession()
    32  			defer sess.Close()
    33  			if err := sess.Begin(); err != nil {
    34  				return fmt.Errorf("unable to allow start session. Error: %w", err)
    35  			}
    36  			for _, record := range buf {
    37  				if err := process(sess, record); err != nil {
    38  					return err
    39  				}
    40  			}
    41  			return sess.Commit()
    42  		}()
    43  		if err != nil {
    44  			return err
    45  		}
    46  
    47  		if len(buf) < size {
    48  			return nil
    49  		}
    50  		start += size
    51  		buf = buf[:0]
    52  	}
    53  }
    54  
    55  func AddHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error {
    56  	// Add the column to the table
    57  	type Webhook struct {
    58  		ID   int64  `xorm:"pk autoincr"`
    59  		Type string `xorm:"VARCHAR(16) 'type'"`
    60  		Meta string `xorm:"TEXT"` // store hook-specific attributes
    61  
    62  		// HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
    63  		HeaderAuthorizationEncrypted string `xorm:"TEXT"`
    64  	}
    65  	err := x.Sync(new(Webhook))
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	// Migrate the matrix webhooks
    71  
    72  	type MatrixMeta struct {
    73  		HomeserverURL string `json:"homeserver_url"`
    74  		Room          string `json:"room_id"`
    75  		MessageType   int    `json:"message_type"`
    76  	}
    77  	type MatrixMetaWithAccessToken struct {
    78  		MatrixMeta
    79  		AccessToken string `json:"access_token"`
    80  	}
    81  
    82  	err = batchProcess(x,
    83  		make([]*Webhook, 0, 50),
    84  		func(limit, start int) *xorm.Session {
    85  			return x.Where("type=?", "matrix").OrderBy("id").Limit(limit, start)
    86  		},
    87  		func(sess *xorm.Session, hook *Webhook) error {
    88  			// retrieve token from meta
    89  			var withToken MatrixMetaWithAccessToken
    90  			err := json.Unmarshal([]byte(hook.Meta), &withToken)
    91  			if err != nil {
    92  				return fmt.Errorf("unable to unmarshal matrix meta for webhook[id=%d]: %w", hook.ID, err)
    93  			}
    94  			if withToken.AccessToken == "" {
    95  				return nil
    96  			}
    97  
    98  			// encrypt token
    99  			authorization := "Bearer " + withToken.AccessToken
   100  			hook.HeaderAuthorizationEncrypted, err = secret.EncryptSecret(setting.SecretKey, authorization)
   101  			if err != nil {
   102  				return fmt.Errorf("unable to encrypt access token for webhook[id=%d]: %w", hook.ID, err)
   103  			}
   104  
   105  			// remove token from meta
   106  			withoutToken, err := json.Marshal(withToken.MatrixMeta)
   107  			if err != nil {
   108  				return fmt.Errorf("unable to marshal matrix meta for webhook[id=%d]: %w", hook.ID, err)
   109  			}
   110  			hook.Meta = string(withoutToken)
   111  
   112  			// save in database
   113  			count, err := sess.ID(hook.ID).Cols("meta", "header_authorization_encrypted").Update(hook)
   114  			if count != 1 || err != nil {
   115  				return fmt.Errorf("unable to update header_authorization_encrypted for webhook[id=%d]: %d,%w", hook.ID, count, err)
   116  			}
   117  			return nil
   118  		})
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	// Remove access_token from HookTask
   124  
   125  	type HookTask struct {
   126  		ID             int64 `xorm:"pk autoincr"`
   127  		HookID         int64
   128  		PayloadContent string `xorm:"LONGTEXT"`
   129  	}
   130  
   131  	type MatrixPayloadSafe struct {
   132  		Body          string               `json:"body"`
   133  		MsgType       string               `json:"msgtype"`
   134  		Format        string               `json:"format"`
   135  		FormattedBody string               `json:"formatted_body"`
   136  		Commits       []*api.PayloadCommit `json:"io.gitea.commits,omitempty"`
   137  	}
   138  	type MatrixPayloadUnsafe struct {
   139  		MatrixPayloadSafe
   140  		AccessToken string `json:"access_token"`
   141  	}
   142  
   143  	err = batchProcess(x,
   144  		make([]*HookTask, 0, 50),
   145  		func(limit, start int) *xorm.Session {
   146  			return x.Where(builder.And(
   147  				builder.In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"type": "matrix"})),
   148  				builder.Like{"payload_content", "access_token"},
   149  			)).OrderBy("id").Limit(limit, 0) // ignore the provided "start", since other payload were already converted and don't contain 'payload_content' anymore
   150  		},
   151  		func(sess *xorm.Session, hookTask *HookTask) error {
   152  			// retrieve token from payload_content
   153  			var withToken MatrixPayloadUnsafe
   154  			err := json.Unmarshal([]byte(hookTask.PayloadContent), &withToken)
   155  			if err != nil {
   156  				return fmt.Errorf("unable to unmarshal payload_content for hook_task[id=%d]: %w", hookTask.ID, err)
   157  			}
   158  			if withToken.AccessToken == "" {
   159  				return nil
   160  			}
   161  
   162  			// remove token from payload_content
   163  			withoutToken, err := json.Marshal(withToken.MatrixPayloadSafe)
   164  			if err != nil {
   165  				return fmt.Errorf("unable to marshal payload_content for hook_task[id=%d]: %w", hookTask.ID, err)
   166  			}
   167  			hookTask.PayloadContent = string(withoutToken)
   168  
   169  			// save in database
   170  			count, err := sess.ID(hookTask.ID).Cols("payload_content").Update(hookTask)
   171  			if count != 1 || err != nil {
   172  				return fmt.Errorf("unable to update payload_content for hook_task[id=%d]: %d,%w", hookTask.ID, count, err)
   173  			}
   174  			return nil
   175  		})
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	return nil
   181  }