code.gitea.io/gitea@v1.22.3/tests/integration/api_admin_test.go (about)

     1  // Copyright 2017 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"testing"
    10  	"time"
    11  
    12  	asymkey_model "code.gitea.io/gitea/models/asymkey"
    13  	auth_model "code.gitea.io/gitea/models/auth"
    14  	"code.gitea.io/gitea/models/unittest"
    15  	user_model "code.gitea.io/gitea/models/user"
    16  	"code.gitea.io/gitea/modules/json"
    17  	"code.gitea.io/gitea/modules/setting"
    18  	api "code.gitea.io/gitea/modules/structs"
    19  	"code.gitea.io/gitea/tests"
    20  
    21  	"github.com/gobwas/glob"
    22  	"github.com/stretchr/testify/assert"
    23  )
    24  
    25  func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {
    26  	defer tests.PrepareTestEnv(t)()
    27  	// user1 is an admin user
    28  	session := loginUser(t, "user1")
    29  	keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
    30  
    31  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
    32  	urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", keyOwner.Name)
    33  	req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
    34  		"key":   "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
    35  		"title": "test-key",
    36  	}).AddTokenAuth(token)
    37  	resp := MakeRequest(t, req, http.StatusCreated)
    38  
    39  	var newPublicKey api.PublicKey
    40  	DecodeJSON(t, resp, &newPublicKey)
    41  	unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{
    42  		ID:          newPublicKey.ID,
    43  		Name:        newPublicKey.Title,
    44  		Fingerprint: newPublicKey.Fingerprint,
    45  		OwnerID:     keyOwner.ID,
    46  	})
    47  
    48  	req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", keyOwner.Name, newPublicKey.ID).
    49  		AddTokenAuth(token)
    50  	MakeRequest(t, req, http.StatusNoContent)
    51  	unittest.AssertNotExistsBean(t, &asymkey_model.PublicKey{ID: newPublicKey.ID})
    52  }
    53  
    54  func TestAPIAdminDeleteMissingSSHKey(t *testing.T) {
    55  	defer tests.PrepareTestEnv(t)()
    56  
    57  	// user1 is an admin user
    58  	token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteAdmin)
    59  	req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d", unittest.NonexistentID).
    60  		AddTokenAuth(token)
    61  	MakeRequest(t, req, http.StatusNotFound)
    62  }
    63  
    64  func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) {
    65  	defer tests.PrepareTestEnv(t)()
    66  	adminUsername := "user1"
    67  	normalUsername := "user2"
    68  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
    69  
    70  	urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", adminUsername)
    71  	req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
    72  		"key":   "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
    73  		"title": "test-key",
    74  	}).AddTokenAuth(token)
    75  	resp := MakeRequest(t, req, http.StatusCreated)
    76  	var newPublicKey api.PublicKey
    77  	DecodeJSON(t, resp, &newPublicKey)
    78  
    79  	token = getUserToken(t, normalUsername)
    80  	req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID).
    81  		AddTokenAuth(token)
    82  	MakeRequest(t, req, http.StatusForbidden)
    83  }
    84  
    85  func TestAPISudoUser(t *testing.T) {
    86  	defer tests.PrepareTestEnv(t)()
    87  	adminUsername := "user1"
    88  	normalUsername := "user2"
    89  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser)
    90  
    91  	req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user?sudo=%s", normalUsername)).
    92  		AddTokenAuth(token)
    93  	resp := MakeRequest(t, req, http.StatusOK)
    94  	var user api.User
    95  	DecodeJSON(t, resp, &user)
    96  
    97  	assert.Equal(t, normalUsername, user.UserName)
    98  }
    99  
   100  func TestAPISudoUserForbidden(t *testing.T) {
   101  	defer tests.PrepareTestEnv(t)()
   102  	adminUsername := "user1"
   103  	normalUsername := "user2"
   104  
   105  	token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin)
   106  	req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user?sudo=%s", adminUsername)).
   107  		AddTokenAuth(token)
   108  	MakeRequest(t, req, http.StatusForbidden)
   109  }
   110  
   111  func TestAPIListUsers(t *testing.T) {
   112  	defer tests.PrepareTestEnv(t)()
   113  	adminUsername := "user1"
   114  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin)
   115  
   116  	req := NewRequest(t, "GET", "/api/v1/admin/users").
   117  		AddTokenAuth(token)
   118  	resp := MakeRequest(t, req, http.StatusOK)
   119  	var users []api.User
   120  	DecodeJSON(t, resp, &users)
   121  
   122  	found := false
   123  	for _, user := range users {
   124  		if user.UserName == adminUsername {
   125  			found = true
   126  		}
   127  	}
   128  	assert.True(t, found)
   129  	numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0")
   130  	assert.Len(t, users, numberOfUsers)
   131  }
   132  
   133  func TestAPIListUsersNotLoggedIn(t *testing.T) {
   134  	defer tests.PrepareTestEnv(t)()
   135  	req := NewRequest(t, "GET", "/api/v1/admin/users")
   136  	MakeRequest(t, req, http.StatusUnauthorized)
   137  }
   138  
   139  func TestAPIListUsersNonAdmin(t *testing.T) {
   140  	defer tests.PrepareTestEnv(t)()
   141  	nonAdminUsername := "user2"
   142  	token := getUserToken(t, nonAdminUsername)
   143  	req := NewRequest(t, "GET", "/api/v1/admin/users").
   144  		AddTokenAuth(token)
   145  	MakeRequest(t, req, http.StatusForbidden)
   146  }
   147  
   148  func TestAPICreateUserInvalidEmail(t *testing.T) {
   149  	defer tests.PrepareTestEnv(t)()
   150  	adminUsername := "user1"
   151  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
   152  	req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{
   153  		"email":                "invalid_email@domain.com\r\n",
   154  		"full_name":            "invalid user",
   155  		"login_name":           "invalidUser",
   156  		"must_change_password": "true",
   157  		"password":             "password",
   158  		"send_notify":          "true",
   159  		"source_id":            "0",
   160  		"username":             "invalidUser",
   161  	}).AddTokenAuth(token)
   162  	MakeRequest(t, req, http.StatusUnprocessableEntity)
   163  }
   164  
   165  func TestAPICreateAndDeleteUser(t *testing.T) {
   166  	defer tests.PrepareTestEnv(t)()
   167  	adminUsername := "user1"
   168  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
   169  
   170  	req := NewRequestWithValues(
   171  		t,
   172  		"POST",
   173  		"/api/v1/admin/users",
   174  		map[string]string{
   175  			"email":                "deleteme@domain.com",
   176  			"full_name":            "delete me",
   177  			"login_name":           "deleteme",
   178  			"must_change_password": "true",
   179  			"password":             "password",
   180  			"send_notify":          "true",
   181  			"source_id":            "0",
   182  			"username":             "deleteme",
   183  		},
   184  	).AddTokenAuth(token)
   185  	MakeRequest(t, req, http.StatusCreated)
   186  
   187  	req = NewRequest(t, "DELETE", "/api/v1/admin/users/deleteme").
   188  		AddTokenAuth(token)
   189  	MakeRequest(t, req, http.StatusNoContent)
   190  }
   191  
   192  func TestAPIEditUser(t *testing.T) {
   193  	defer tests.PrepareTestEnv(t)()
   194  	adminUsername := "user1"
   195  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
   196  	urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2")
   197  
   198  	fullNameToChange := "Full Name User 2"
   199  	req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
   200  		// required
   201  		"login_name": "user2",
   202  		"source_id":  "0",
   203  		// to change
   204  		"full_name": fullNameToChange,
   205  	}).AddTokenAuth(token)
   206  	MakeRequest(t, req, http.StatusOK)
   207  	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"})
   208  	assert.Equal(t, fullNameToChange, user2.FullName)
   209  
   210  	empty := ""
   211  	req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
   212  		LoginName: "user2",
   213  		SourceID:  0,
   214  		Email:     &empty,
   215  	}).AddTokenAuth(token)
   216  	resp := MakeRequest(t, req, http.StatusBadRequest)
   217  
   218  	errMap := make(map[string]any)
   219  	json.Unmarshal(resp.Body.Bytes(), &errMap)
   220  	assert.EqualValues(t, "e-mail invalid [email: ]", errMap["message"].(string))
   221  
   222  	user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"})
   223  	assert.False(t, user2.IsRestricted)
   224  	bTrue := true
   225  	req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
   226  		// required
   227  		LoginName: "user2",
   228  		SourceID:  0,
   229  		// to change
   230  		Restricted: &bTrue,
   231  	}).AddTokenAuth(token)
   232  	MakeRequest(t, req, http.StatusOK)
   233  	user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"})
   234  	assert.True(t, user2.IsRestricted)
   235  }
   236  
   237  func TestAPICreateRepoForUser(t *testing.T) {
   238  	defer tests.PrepareTestEnv(t)()
   239  	adminUsername := "user1"
   240  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
   241  
   242  	req := NewRequestWithJSON(
   243  		t,
   244  		"POST",
   245  		fmt.Sprintf("/api/v1/admin/users/%s/repos", adminUsername),
   246  		&api.CreateRepoOption{
   247  			Name: "admincreatedrepo",
   248  		},
   249  	).AddTokenAuth(token)
   250  	MakeRequest(t, req, http.StatusCreated)
   251  }
   252  
   253  func TestAPIRenameUser(t *testing.T) {
   254  	defer tests.PrepareTestEnv(t)()
   255  	adminUsername := "user1"
   256  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
   257  	urlStr := fmt.Sprintf("/api/v1/admin/users/%s/rename", "user2")
   258  	req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
   259  		// required
   260  		"new_name": "User2",
   261  	}).AddTokenAuth(token)
   262  	MakeRequest(t, req, http.StatusNoContent)
   263  
   264  	urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2")
   265  	req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
   266  		// required
   267  		"new_name": "User2-2-2",
   268  	}).AddTokenAuth(token)
   269  	MakeRequest(t, req, http.StatusNoContent)
   270  
   271  	req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
   272  		// required
   273  		"new_name": "user1",
   274  	}).AddTokenAuth(token)
   275  	// the old user name still be used by with a redirect
   276  	MakeRequest(t, req, http.StatusTemporaryRedirect)
   277  
   278  	urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2-2-2")
   279  	req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
   280  		// required
   281  		"new_name": "user1",
   282  	}).AddTokenAuth(token)
   283  	MakeRequest(t, req, http.StatusUnprocessableEntity)
   284  
   285  	req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
   286  		// required
   287  		"new_name": "user2",
   288  	}).AddTokenAuth(token)
   289  	MakeRequest(t, req, http.StatusNoContent)
   290  }
   291  
   292  func TestAPICron(t *testing.T) {
   293  	defer tests.PrepareTestEnv(t)()
   294  
   295  	// user1 is an admin user
   296  	session := loginUser(t, "user1")
   297  
   298  	t.Run("List", func(t *testing.T) {
   299  		defer tests.PrintCurrentTest(t)()
   300  
   301  		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin)
   302  
   303  		req := NewRequest(t, "GET", "/api/v1/admin/cron").
   304  			AddTokenAuth(token)
   305  		resp := MakeRequest(t, req, http.StatusOK)
   306  
   307  		assert.Equal(t, "28", resp.Header().Get("X-Total-Count"))
   308  
   309  		var crons []api.Cron
   310  		DecodeJSON(t, resp, &crons)
   311  		assert.Len(t, crons, 28)
   312  	})
   313  
   314  	t.Run("Execute", func(t *testing.T) {
   315  		defer tests.PrintCurrentTest(t)()
   316  
   317  		now := time.Now()
   318  		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
   319  		// Archive cleanup is harmless, because in the test environment there are none
   320  		// and is thus an NOOP operation and therefore doesn't interfere with any other
   321  		// tests.
   322  		req := NewRequest(t, "POST", "/api/v1/admin/cron/archive_cleanup").
   323  			AddTokenAuth(token)
   324  		MakeRequest(t, req, http.StatusNoContent)
   325  
   326  		// Check for the latest run time for this cron, to ensure it has been run.
   327  		req = NewRequest(t, "GET", "/api/v1/admin/cron").
   328  			AddTokenAuth(token)
   329  		resp := MakeRequest(t, req, http.StatusOK)
   330  
   331  		var crons []api.Cron
   332  		DecodeJSON(t, resp, &crons)
   333  
   334  		for _, cron := range crons {
   335  			if cron.Name == "archive_cleanup" {
   336  				assert.True(t, now.Before(cron.Prev))
   337  			}
   338  		}
   339  	})
   340  }
   341  
   342  func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) {
   343  	defer tests.PrepareTestEnv(t)()
   344  
   345  	setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")}
   346  	defer func() {
   347  		setting.Service.EmailDomainAllowList = []glob.Glob{}
   348  	}()
   349  
   350  	adminUsername := "user1"
   351  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
   352  
   353  	req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{
   354  		"email":                "allowedUser1@example1.org",
   355  		"login_name":           "allowedUser1",
   356  		"username":             "allowedUser1",
   357  		"password":             "allowedUser1_pass",
   358  		"must_change_password": "true",
   359  	}).AddTokenAuth(token)
   360  	resp := MakeRequest(t, req, http.StatusCreated)
   361  	assert.Equal(t, "the domain of user email allowedUser1@example1.org conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", resp.Header().Get("X-Gitea-Warning"))
   362  
   363  	req = NewRequest(t, "DELETE", "/api/v1/admin/users/allowedUser1").AddTokenAuth(token)
   364  	MakeRequest(t, req, http.StatusNoContent)
   365  }
   366  
   367  func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) {
   368  	defer tests.PrepareTestEnv(t)()
   369  
   370  	setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")}
   371  	defer func() {
   372  		setting.Service.EmailDomainAllowList = []glob.Glob{}
   373  	}()
   374  
   375  	adminUsername := "user1"
   376  	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
   377  	urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2")
   378  
   379  	newEmail := "user2@example1.com"
   380  	req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
   381  		LoginName: "user2",
   382  		SourceID:  0,
   383  		Email:     &newEmail,
   384  	}).AddTokenAuth(token)
   385  	resp := MakeRequest(t, req, http.StatusOK)
   386  	assert.Equal(t, "the domain of user email user2@example1.com conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", resp.Header().Get("X-Gitea-Warning"))
   387  
   388  	originalEmail := "user2@example.com"
   389  	req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
   390  		LoginName: "user2",
   391  		SourceID:  0,
   392  		Email:     &originalEmail,
   393  	}).AddTokenAuth(token)
   394  	MakeRequest(t, req, http.StatusOK)
   395  }