Repository: xanzy/go-gitlab
Pull request #1301 (2021-12-01 21:24:32)

Add support for User `current_sign_in_ip` and `last_sign_in_ip`
The available fields when getting/listing a GitLab User vary when the request is performed as a `regular` user vs an `admin`:

`Regular (User)`
* https://docs.gitlab.com/ee/api/users.html#for-user

`Admin`
* https://docs.gitlab.com/ee/api/users.html#for-admin

One such variation is the `current_sign_in_ip` and `last_sign_in_ip` fields.

Adding them to the struct appears to correctly return an IP address when the request is made with an `admin` token and `<nil>` when requested by a regular user token.


Patch:
diff --git a/users.go b/users.go
index d179a09..9632c14 100644
--- a/users.go
+++ b/users.go
@@ -19,6 +19,7 @@ package gitlab
 import (
 	"errors"
 	"fmt"
+	"net"
 	"net/http"
 	"time"
 )
@@ -85,7 +86,9 @@ type User struct {
 	CanCreateProject               bool               `json:"can_create_project"`
 	ProjectsLimit                  int                `json:"projects_limit"`
 	CurrentSignInAt                *time.Time         `json:"current_sign_in_at"`
+	CurrentSignInIP                *net.IP            `json:"current_sign_in_ip"`
 	LastSignInAt                   *time.Time         `json:"last_sign_in_at"`
+	LastSignInIP                   *net.IP            `json:"last_sign_in_ip"`
 	ConfirmedAt                    *time.Time         `json:"confirmed_at"`
 	TwoFactorEnabled               bool               `json:"two_factor_enabled"`
 	Note                           string             `json:"note"`


Test patch:
diff --git a/testdata/get_user.json b/testdata/get_user.json
new file mode 100644
index 0000000..673d1b4
--- /dev/null
+++ b/testdata/get_user.json
@@ -0,0 +1,21 @@
+{
+  "id": 1,
+  "username": "john_smith",
+  "name": "John Smith",
+  "state": "active",
+  "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+  "web_url": "http://localhost:3000/john_smith",
+  "created_at": "2012-05-23T08:00:58Z",
+  "bio": "Bio of John Smith",
+  "bot": false,
+  "location": "USA",
+  "public_email": "john@example.com",
+  "skype": "john_smith",
+  "linkedin": "john_smith",
+  "twitter": "john_smith",
+  "website_url": "john_smith.example.com",
+  "organization": "Smith Inc",
+  "job_title": "Operations Specialist",
+  "followers": 1,
+  "following": 1
+}
diff --git a/testdata/get_user_admin.json b/testdata/get_user_admin.json
new file mode 100644
index 0000000..29d5a42
--- /dev/null
+++ b/testdata/get_user_admin.json
@@ -0,0 +1,42 @@
+{
+  "id": 1,
+  "username": "john_smith",
+  "email": "john@example.com",
+  "name": "John Smith",
+  "state": "active",
+  "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+  "web_url": "http://localhost:3000/john_smith",
+  "created_at": "2012-05-23T08:00:58Z",
+  "is_admin": true,
+  "bio": "Bio of John Smith",
+  "location": "USA",
+  "public_email": "john@example.com",
+  "skype": "john_smith",
+  "linkedin": "john_smith",
+  "twitter": "john_smith",
+  "website_url": "john_smith.example.com",
+  "organization": "Smith Inc",
+  "job_title": "Operations Specialist",
+  "last_sign_in_at": "2012-06-01T11:41:01Z",
+  "confirmed_at": "2012-05-23T09:05:22Z",
+  "theme_id": 1,
+  "last_activity_on": "2012-05-23",
+  "color_scheme_id": 2,
+  "projects_limit": 100,
+  "current_sign_in_at": "2012-06-02T06:36:55Z",
+  "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123",
+  "identities": [
+    {"provider": "github", "extern_uid": "2435223452345"}
+  ],
+  "can_create_group": true,
+  "can_create_project": true,
+  "two_factor_enabled": true,
+  "external": false,
+  "private_profile": false,
+  "commit_email": "john-codes@example.com",
+  "current_sign_in_ip": "8.8.8.8",
+  "last_sign_in_ip": "2001:db8::68",
+  "plan": "gold",
+  "trial": true,
+  "sign_in_count": 1337
+}
diff --git a/users_test.go b/users_test.go
index 14b18a0..21aa6be 100644
--- a/users_test.go
+++ b/users_test.go
@@ -19,13 +19,105 @@ package gitlab
 import (
 	"errors"
 	"fmt"
+	"net"
 	"net/http"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
+func TestGetUser(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	path := "/api/v4/users/1"
+
+	mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		mustWriteHTTPResponse(t, w, "testdata/get_user.json")
+	})
+
+	user, _, err := client.Users.GetUser(1, GetUsersOptions{})
+	require.NoError(t, err)
+
+	want := &User{
+		ID:           1,
+		Username:     "john_smith",
+		Name:         "John Smith",
+		State:        "active",
+		WebURL:       "http://localhost:3000/john_smith",
+		CreatedAt:    Time(time.Date(2012, time.May, 23, 8, 00, 58, 0, time.UTC)),
+		Bio:          "Bio of John Smith",
+		Location:     "USA",
+		PublicEmail:  "john@example.com",
+		Skype:        "john_smith",
+		Linkedin:     "john_smith",
+		Twitter:      "john_smith",
+		WebsiteURL:   "john_smith.example.com",
+		Organization: "Smith Inc",
+		JobTitle:     "Operations Specialist",
+		AvatarURL:    "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+	}
+	require.Equal(t, want, user)
+}
+
+func TestGetUserAdmin(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	path := "/api/v4/users/1"
+
+	mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		mustWriteHTTPResponse(t, w, "testdata/get_user_admin.json")
+	})
+
+	user, _, err := client.Users.GetUser(1, GetUsersOptions{})
+	require.NoError(t, err)
+
+	lastActivityOn := ISOTime(time.Date(2012, time.May, 23, 0, 0, 0, 0, time.UTC))
+	currentSignInIP := net.ParseIP("8.8.8.8")
+	lastSignInIP := net.ParseIP("2001:db8::68")
+
+	want := &User{
+		ID:               1,
+		Username:         "john_smith",
+		Email:            "john@example.com",
+		Name:             "John Smith",
+		State:            "active",
+		WebURL:           "http://localhost:3000/john_smith",
+		CreatedAt:        Time(time.Date(2012, time.May, 23, 8, 0, 58, 0, time.UTC)),
+		Bio:              "Bio of John Smith",
+		Location:         "USA",
+		PublicEmail:      "john@example.com",
+		Skype:            "john_smith",
+		Linkedin:         "john_smith",
+		Twitter:          "john_smith",
+		WebsiteURL:       "john_smith.example.com",
+		Organization:     "Smith Inc",
+		JobTitle:         "Operations Specialist",
+		ThemeID:          1,
+		LastActivityOn:   &lastActivityOn,
+		ColorSchemeID:    2,
+		IsAdmin:          true,
+		AvatarURL:        "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+		CanCreateGroup:   true,
+		CanCreateProject: true,
+		ProjectsLimit:    100,
+		CurrentSignInAt:  Time(time.Date(2012, time.June, 2, 6, 36, 55, 0, time.UTC)),
+		CurrentSignInIP:  &currentSignInIP,
+		LastSignInAt:     Time(time.Date(2012, time.June, 1, 11, 41, 1, 0, time.UTC)),
+		LastSignInIP:     &lastSignInIP,
+		ConfirmedAt:      Time(time.Date(2012, time.May, 23, 9, 05, 22, 0, time.UTC)),
+		TwoFactorEnabled: true,
+		Note:             "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123",
+		Identities:       []*UserIdentity{&UserIdentity{Provider: "github", ExternUID: "2435223452345"}},
+	}
+	require.Equal(t, want, user)
+}
+
 func TestBlockUser(t *testing.T) {
 	mux, server, client := setup(t)
 	defer teardown(server)