diff --git a/authn/authn.go b/authn/authn.go index f58a37a..1b0c33e 100644 --- a/authn/authn.go +++ b/authn/authn.go @@ -1,48 +1,29 @@ package authn import ( - "errors" + "context" "net/http" - "time" + "strconv" jwt "gopkg.in/square/go-jose.v2/jwt" ) // TODO: jose/jwt references are all over the place. Refactor possible? -// ErrInvalidOptions is returned by SubjectFrom if invalid options are used -var ErrInvalidOptions = errors.New("invalid options for SubjectFrom") - // Client provides JWT verification for ID tokens generated by the AuthN server. In the future it // will also implement the server's private APIs (aka admin actions). type Client struct { - config Config - iclient *internalClient - kchain *keychainCache - verifier JWTClaimsExtractor + cli *Client2 } // NewClient returns an initialized and configured Client. func NewClient(config Config) (*Client, error) { - var err error - config.setDefaults() - - ac := Client{} - - ac.config = config - - ac.iclient, err = newInternalClient(config.PrivateBaseURL, config.Username, config.Password) + cli, err := NewClient2(config) if err != nil { return nil, err } - ac.kchain = newKeychainCache(time.Duration(config.KeychainTTL)*time.Minute, ac.iclient) - ac.verifier, err = NewIDTokenVerifier(config.Issuer, config.Audience, ac.kchain) - if err != nil { - return nil, err - } - - return &ac, nil + return &Client{cli}, nil } // SubjectFrom will return the subject inside the given idToken if and only if the token is a valid @@ -51,71 +32,82 @@ func NewClient(config Config) (*Client, error) { // // If the JWT does not verify, the returned error will explain why. This is for debugging purposes. func (ac *Client) SubjectFrom(idToken string) (string, error) { - return ac.subjectFromVerifier(idToken, ac.verifier) + return ac.cli.SubjectFrom(idToken) } // SubjectFromWithAudience works like SubjectFrom but allows specifying a different // JWT audience func (ac *Client) SubjectFromWithAudience(idToken string, audience jwt.Audience) (string, error) { - verifier, err := newIDTokenVerifierWithAudiences(ac.config.Issuer, audience, ac.kchain) - if err != nil { - return "", err - } - - return ac.subjectFromVerifier(idToken, verifier) -} - -func (ac *Client) subjectFromVerifier(idToken string, verifier JWTClaimsExtractor) (string, error) { - claims, err := verifier.GetVerifiedClaims(idToken) - if err != nil { - return "", err - } - return claims.Subject, nil + return ac.cli.SubjectFromWithAudience(idToken, audience) } // GetAccount gets the account with the associated id func (ac *Client) GetAccount(id string) (*Account, error) { //Should this be a string or an int? - return ac.iclient.GetAccount(id) + accountID, err := strconv.Atoi(id) + if err != nil { + return nil, err + } + return ac.cli.GetAccount(context.Background(), accountID) } // Update updates the account with the associated id func (ac *Client) Update(id, username string) error { - return ac.iclient.Update(id, username) + accountID, err := strconv.Atoi(id) + if err != nil { + return err + } + return ac.cli.Update(context.Background(), accountID, username) } // LockAccount locks the account with the associated id func (ac *Client) LockAccount(id string) error { - return ac.iclient.LockAccount(id) + accountID, err := strconv.Atoi(id) + if err != nil { + return err + } + return ac.cli.LockAccount(context.Background(), accountID) } // UnlockAccount unlocks the account with the associated id func (ac *Client) UnlockAccount(id string) error { - return ac.iclient.UnlockAccount(id) + accountID, err := strconv.Atoi(id) + if err != nil { + return err + } + return ac.cli.UnlockAccount(context.Background(), accountID) } // ArchiveAccount archives the account with the associated id func (ac *Client) ArchiveAccount(id string) error { - return ac.iclient.ArchiveAccount(id) + accountID, err := strconv.Atoi(id) + if err != nil { + return err + } + return ac.cli.ArchiveAccount(context.Background(), accountID) } // ImportAccount imports an account with the provided information, returns the imported account id func (ac *Client) ImportAccount(username, password string, locked bool) (int, error) { - return ac.iclient.ImportAccount(username, password, locked) + return ac.cli.ImportAccount(context.Background(), username, password, locked) } // ExpirePassword expires the password of the account with the associated id func (ac *Client) ExpirePassword(id string) error { - return ac.iclient.ExpirePassword(id) + accountID, err := strconv.Atoi(id) + if err != nil { + return err + } + return ac.cli.ExpirePassword(context.Background(), accountID) } // ServiceStats gets the http response object from calling the service stats endpoint func (ac *Client) ServiceStats() (*http.Response, error) { - return ac.iclient.ServiceStats() + return ac.cli.ServiceStats(context.Background()) } // ServerStats gets the http response object from calling the server stats endpoint func (ac *Client) ServerStats() (*http.Response, error) { - return ac.iclient.ServerStats() + return ac.cli.ServerStats(context.Background()) } // DefaultClient can be initialized by Configure and used by SubjectFrom. diff --git a/authn/client_v2.go b/authn/client_v2.go new file mode 100644 index 0000000..ccd6770 --- /dev/null +++ b/authn/client_v2.go @@ -0,0 +1,116 @@ +package authn + +import ( + "context" + "net/http" + "time" + + jwt "gopkg.in/square/go-jose.v2/jwt" +) + +// Client2 provides JWT verification for ID tokens generated by the AuthN server. +// It also implements the server's private APIs (aka admin actions) +type Client2 struct { + config Config + iclient *internalClient + kchain *keychainCache + verifier JWTClaimsExtractor +} + +// NewClient2 returns an initialized and configured Client2 +func NewClient2(config Config) (*Client2, error) { + config.setDefaults() + + cli, err := newInternalClient(config.PrivateBaseURL, config.Username, config.Password) + if err != nil { + return nil, err + } + + ttl := time.Duration(config.KeychainTTL) * time.Minute + kchain := newKeychainCache(ttl, cli) + + verifier, err := NewIDTokenVerifier(config.Issuer, config.Audience, kchain) + if err != nil { + return nil, err + } + + return &Client2{ + config: config, + iclient: cli, + kchain: kchain, + verifier: verifier, + }, nil +} + +// SubjectFrom will return the subject inside the given idToken if and only if the token is a valid +// JWT that passes all verification requirements. The returned value is the AuthN server's account +// ID and should be used as a unique foreign key in your users data. +// +// If the JWT does not verify, the returned error will explain why. This is for debugging purposes. +func (ac *Client2) SubjectFrom(idToken string) (string, error) { + return ac.subjectFromVerifier(idToken, ac.verifier) +} + +// SubjectFromWithAudience works like SubjectFrom but allows specifying a different +// JWT audience +func (ac *Client2) SubjectFromWithAudience(idToken string, audience jwt.Audience) (string, error) { + verifier, err := newIDTokenVerifierWithAudiences(ac.config.Issuer, audience, ac.kchain) + if err != nil { + return "", err + } + + return ac.subjectFromVerifier(idToken, verifier) +} + +func (ac *Client2) subjectFromVerifier(idToken string, verifier JWTClaimsExtractor) (string, error) { + claims, err := verifier.GetVerifiedClaims(idToken) + if err != nil { + return "", err + } + return claims.Subject, nil +} + +// GetAccount gets the account with the associated id +func (ac *Client2) GetAccount(ctx context.Context, id int) (*Account, error) { + return ac.iclient.GetAccount(ctx, id) +} + +// Update updates the account with the associated id +func (ac *Client2) Update(ctx context.Context, id int, username string) error { + return ac.iclient.Update(ctx, id, username) +} + +// LockAccount locks the account with the associated id +func (ac *Client2) LockAccount(ctx context.Context, id int) error { + return ac.iclient.LockAccount(ctx, id) +} + +// UnlockAccount unlocks the account with the associated id +func (ac *Client2) UnlockAccount(ctx context.Context, id int) error { + return ac.iclient.UnlockAccount(ctx, id) +} + +// ArchiveAccount archives the account with the associated id +func (ac *Client2) ArchiveAccount(ctx context.Context, id int) error { + return ac.iclient.ArchiveAccount(ctx, id) +} + +// ImportAccount imports an account with the provided information, returns the imported account id +func (ac *Client2) ImportAccount(ctx context.Context, username, password string, locked bool) (int, error) { + return ac.iclient.ImportAccount(ctx, username, password, locked) +} + +// ExpirePassword expires the password of the account with the associated id +func (ac *Client2) ExpirePassword(ctx context.Context, id int) error { + return ac.iclient.ExpirePassword(ctx, id) +} + +// ServiceStats gets the http response object from calling the service stats endpoint +func (ac *Client2) ServiceStats(ctx context.Context) (*http.Response, error) { + return ac.iclient.ServiceStats(ctx) +} + +// ServerStats gets the http response object from calling the server stats endpoint +func (ac *Client2) ServerStats(ctx context.Context) (*http.Response, error) { + return ac.iclient.ServerStats(ctx) +} diff --git a/authn/internal_client.go b/authn/internal_client.go index 9feb213..57f99de 100644 --- a/authn/internal_client.go +++ b/authn/internal_client.go @@ -1,6 +1,7 @@ package authn import ( + "context" "encoding/json" "fmt" "io" @@ -75,9 +76,10 @@ func (ic *internalClient) Key(kid string) ([]jose.JSONWebKey, error) { return jwks.Key(kid), nil } -//GetAccount gets the account details for the specified account id -func (ic *internalClient) GetAccount(id string) (*Account, error) { - resp, err := ic.doWithAuth(get, "accounts/"+id, nil) +// GetAccount gets the account details for the specified account id +func (ic *internalClient) GetAccount(ctx context.Context, id int) (*Account, error) { + url := fmt.Sprintf("accounts/%d", id) + resp, err := ic.doWithAuth(ctx, get, url, nil) if err != nil { return nil, err } @@ -95,41 +97,45 @@ func (ic *internalClient) GetAccount(id string) (*Account, error) { return &data.Result, nil } -//Update updates the account with the specified id -func (ic *internalClient) Update(id, username string) error { +// Update updates the account with the specified id +func (ic *internalClient) Update(ctx context.Context, id int, username string) error { form := url.Values{} form.Add("username", username) - _, err := ic.doWithAuth(patch, "accounts/"+id, strings.NewReader(form.Encode())) + url := fmt.Sprintf("accounts/%d", id) + _, err := ic.doWithAuth(ctx, patch, url, strings.NewReader(form.Encode())) return err } -//LockAccount locks the account with the specified id -func (ic *internalClient) LockAccount(id string) error { - _, err := ic.doWithAuth(patch, "accounts/"+id+"/lock", nil) +// LockAccount locks the account with the specified id +func (ic *internalClient) LockAccount(ctx context.Context, id int) error { + url := fmt.Sprintf("accounts/%d/lock", id) + _, err := ic.doWithAuth(ctx, patch, url, nil) return err } -//UnlockAccount unlocks the account with the specified id -func (ic *internalClient) UnlockAccount(id string) error { - _, err := ic.doWithAuth(patch, "accounts/"+id+"/unlock", nil) +// UnlockAccount unlocks the account with the specified id +func (ic *internalClient) UnlockAccount(ctx context.Context, id int) error { + url := fmt.Sprintf("accounts/%d/unlock", id) + _, err := ic.doWithAuth(ctx, patch, url, nil) return err } -//ArchiveAccount archives the account with the specified id -func (ic *internalClient) ArchiveAccount(id string) error { - _, err := ic.doWithAuth(delete, "accounts/"+id, nil) +// ArchiveAccount archives the account with the specified id +func (ic *internalClient) ArchiveAccount(ctx context.Context, id int) error { + url := fmt.Sprintf("accounts/%d", id) + _, err := ic.doWithAuth(ctx, delete, url, nil) return err } -//ImportAccount imports an existing account -func (ic *internalClient) ImportAccount(username, password string, locked bool) (int, error) { +// ImportAccount imports an existing account +func (ic *internalClient) ImportAccount(ctx context.Context, username, password string, locked bool) (int, error) { form := url.Values{} form.Add("username", username) form.Add("password", password) form.Add("locked", strconv.FormatBool(locked)) - resp, err := ic.doWithAuth(post, "accounts/import", strings.NewReader(form.Encode())) + resp, err := ic.doWithAuth(ctx, post, "accounts/import", strings.NewReader(form.Encode())) if err != nil { return -1, err } @@ -149,20 +155,21 @@ func (ic *internalClient) ImportAccount(username, password string, locked bool) return data.Result.ID, err } -//ExpirePassword expires the users current sessions and flags the account for a required password change on next login -func (ic *internalClient) ExpirePassword(id string) error { - _, err := ic.doWithAuth(patch, "accounts/"+id+"/expire_password", nil) +// ExpirePassword expires the users current sessions and flags the account for a required password change on next login +func (ic *internalClient) ExpirePassword(ctx context.Context, id int) error { + url := fmt.Sprintf("accounts/%d/expire_password", id) + _, err := ic.doWithAuth(ctx, patch, url, nil) return err } -//ServiceStats returns the raw request from the /stats endpoint -func (ic *internalClient) ServiceStats() (*http.Response, error) { - return ic.doWithAuth(get, "stats", nil) +// ServiceStats returns the raw request from the /stats endpoint +func (ic *internalClient) ServiceStats(ctx context.Context) (*http.Response, error) { + return ic.doWithAuth(ctx, get, "stats", nil) } -//ServerStats returns the raw request from the /metrics endpoint -func (ic *internalClient) ServerStats() (*http.Response, error) { - return ic.doWithAuth(get, "metrics", nil) +// ServerStats returns the raw request from the /metrics endpoint +func (ic *internalClient) ServerStats(ctx context.Context) (*http.Response, error) { + return ic.doWithAuth(ctx, get, "metrics", nil) } func (ic *internalClient) absoluteURL(path string) string { @@ -189,11 +196,14 @@ func (ic *internalClient) get(path string, dest interface{}) (int, error) { return resp.StatusCode, nil } -func (ic *internalClient) doWithAuth(verb string, path string, body io.Reader) (*http.Response, error) { +func (ic *internalClient) doWithAuth(ctx context.Context, verb string, path string, body io.Reader) (*http.Response, error) { req, err := http.NewRequest(verb, ic.absoluteURL(path), body) if err != nil { return nil, err } + + req = req.WithContext(ctx) + req.SetBasicAuth(ic.username, ic.password) if verb == post || verb == patch || verb == put { diff --git a/authn/internal_client_test.go b/authn/internal_client_test.go index c926aeb..9bb5a5c 100644 --- a/authn/internal_client_test.go +++ b/authn/internal_client_test.go @@ -2,6 +2,7 @@ package authn import ( "context" + "fmt" "net" "net/http" "net/http/httptest" @@ -55,7 +56,7 @@ func TestICGetAccount(t *testing.T) { url string htusername string htpassword string - id string + id int } type response struct { id int @@ -74,7 +75,7 @@ func TestICGetAccount(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ id: 1, @@ -90,7 +91,7 @@ func TestICGetAccount(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusNotFound, @@ -106,7 +107,7 @@ func TestICGetAccount(t *testing.T) { assert.Equal(t, http.MethodGet, r.Method) assert.Equal(t, tc.request.htusername, username) assert.Equal(t, tc.request.htpassword, password) - assert.Equal(t, "/accounts/"+tc.request.id, r.URL.Path) + assert.Equal(t, fmt.Sprintf("/accounts/%d", tc.request.id), r.URL.Path) w.WriteHeader(tc.response.code) //if we're mocking a good request, return the json if tc.response.code == http.StatusOK { @@ -129,7 +130,7 @@ func TestICGetAccount(t *testing.T) { } cli.client = httpClient - account, err := cli.GetAccount(tc.request.id) + account, err := cli.GetAccount(context.Background(), tc.request.id) if tc.response.errorMsg == "" { //Expecting no error assert.Nil(t, err) assert.Equal(t, tc.response.id, account.ID) @@ -148,7 +149,7 @@ func TestICUpdate(t *testing.T) { url string htusername string htpassword string - id string + id int username string } type response struct { @@ -164,7 +165,7 @@ func TestICUpdate(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, username: "test@test.com", }, response: response{ @@ -177,7 +178,7 @@ func TestICUpdate(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, username: "test@test.com", }, response: response{ @@ -190,7 +191,7 @@ func TestICUpdate(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, username: "test@test.com", }, response: response{ @@ -208,7 +209,7 @@ func TestICUpdate(t *testing.T) { assert.Equal(t, tc.request.htusername, username) assert.Equal(t, tc.request.htpassword, password) assert.Equal(t, tc.request.username, r.PostFormValue("username")) - assert.Equal(t, "/accounts/"+tc.request.id, r.URL.Path) + assert.Equal(t, fmt.Sprintf("/accounts/%d", tc.request.id), r.URL.Path) w.WriteHeader(tc.response.code) }) httpClient, teardown := testingHTTPClient(h) @@ -220,7 +221,7 @@ func TestICUpdate(t *testing.T) { } cli.client = httpClient - err = cli.Update(tc.request.id, tc.request.username) + err = cli.Update(context.Background(), tc.request.id, tc.request.username) if tc.response.errorMsg == "" { //Expecting no error assert.Nil(t, err) } else { //Expecting an error @@ -235,7 +236,7 @@ func TestICLockAccount(t *testing.T) { url string htusername string htpassword string - id string + id int } type response struct { code int @@ -250,7 +251,7 @@ func TestICLockAccount(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusOK, @@ -262,7 +263,7 @@ func TestICLockAccount(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusNotFound, @@ -278,7 +279,7 @@ func TestICLockAccount(t *testing.T) { assert.Equal(t, http.MethodPatch, r.Method) assert.Equal(t, tc.request.htusername, username) assert.Equal(t, tc.request.htpassword, password) - assert.Equal(t, "/accounts/"+tc.request.id+"/lock", r.URL.Path) + assert.Equal(t, fmt.Sprintf("/accounts/%d/lock", tc.request.id), r.URL.Path) w.WriteHeader(tc.response.code) }) httpClient, teardown := testingHTTPClient(h) @@ -290,7 +291,7 @@ func TestICLockAccount(t *testing.T) { } cli.client = httpClient - err = cli.LockAccount(tc.request.id) + err = cli.LockAccount(context.Background(), tc.request.id) if tc.response.errorMsg == "" { //Expecting no error assert.Nil(t, err) } else { //Expecting an error @@ -305,7 +306,7 @@ func TestICUnlockAccount(t *testing.T) { url string htusername string htpassword string - id string + id int } type response struct { code int @@ -320,7 +321,7 @@ func TestICUnlockAccount(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusOK, @@ -332,7 +333,7 @@ func TestICUnlockAccount(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusNotFound, @@ -348,7 +349,7 @@ func TestICUnlockAccount(t *testing.T) { assert.Equal(t, http.MethodPatch, r.Method) assert.Equal(t, tc.request.htusername, username) assert.Equal(t, tc.request.htpassword, password) - assert.Equal(t, "/accounts/"+tc.request.id+"/unlock", r.URL.Path) + assert.Equal(t, fmt.Sprintf("/accounts/%d/unlock", tc.request.id), r.URL.Path) w.WriteHeader(tc.response.code) }) httpClient, teardown := testingHTTPClient(h) @@ -360,7 +361,7 @@ func TestICUnlockAccount(t *testing.T) { } cli.client = httpClient - err = cli.UnlockAccount(tc.request.id) + err = cli.UnlockAccount(context.Background(), tc.request.id) if tc.response.errorMsg == "" { //Expecting no error assert.Nil(t, err) } else { //Expecting an error @@ -375,7 +376,7 @@ func TestICArchiveAccount(t *testing.T) { url string htusername string htpassword string - id string + id int } type response struct { code int @@ -390,7 +391,7 @@ func TestICArchiveAccount(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusOK, @@ -402,7 +403,7 @@ func TestICArchiveAccount(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusNotFound, @@ -418,7 +419,7 @@ func TestICArchiveAccount(t *testing.T) { assert.Equal(t, http.MethodDelete, r.Method) assert.Equal(t, tc.request.htusername, username) assert.Equal(t, tc.request.htpassword, password) - assert.Equal(t, "/accounts/"+tc.request.id, r.URL.Path) + assert.Equal(t, fmt.Sprintf("/accounts/%d", tc.request.id), r.URL.Path) w.WriteHeader(tc.response.code) }) httpClient, teardown := testingHTTPClient(h) @@ -430,7 +431,7 @@ func TestICArchiveAccount(t *testing.T) { } cli.client = httpClient - err = cli.ArchiveAccount(tc.request.id) + err = cli.ArchiveAccount(context.Background(), tc.request.id) if tc.response.errorMsg == "" { //Expecting no error assert.Nil(t, err) } else { //Expecting an error @@ -518,7 +519,7 @@ func TestICImportAccount(t *testing.T) { } cli.client = httpClient - id, err := cli.ImportAccount(tc.request.username, tc.request.password, tc.request.locked) + id, err := cli.ImportAccount(context.Background(), tc.request.username, tc.request.password, tc.request.locked) if tc.response.errorMsg == "" { //Expecting no error assert.Nil(t, err) assert.Equal(t, tc.response.id, id) @@ -534,7 +535,7 @@ func TestICExpirePassword(t *testing.T) { url string htusername string htpassword string - id string + id int } type response struct { code int @@ -549,7 +550,7 @@ func TestICExpirePassword(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusOK, @@ -561,7 +562,7 @@ func TestICExpirePassword(t *testing.T) { url: "http://test.com", htusername: "username", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusNotFound, @@ -577,7 +578,7 @@ func TestICExpirePassword(t *testing.T) { assert.Equal(t, http.MethodPatch, r.Method) assert.Equal(t, tc.request.htusername, username) assert.Equal(t, tc.request.htpassword, password) - assert.Equal(t, "/accounts/"+tc.request.id+"/expire_password", r.URL.Path) + assert.Equal(t, fmt.Sprintf("/accounts/%d/expire_password", tc.request.id), r.URL.Path) w.WriteHeader(tc.response.code) }) httpClient, teardown := testingHTTPClient(h) @@ -589,7 +590,7 @@ func TestICExpirePassword(t *testing.T) { } cli.client = httpClient - err = cli.ExpirePassword(tc.request.id) + err = cli.ExpirePassword(context.Background(), tc.request.id) if tc.response.errorMsg == "" { //Expecting no error assert.Nil(t, err) } else { //Expecting an error @@ -635,7 +636,7 @@ func TestICServiceStats(t *testing.T) { } cli.client = httpClient - _, err = cli.ServiceStats() + _, err = cli.ServiceStats(context.Background()) assert.Nil(t, err) } } @@ -677,7 +678,7 @@ func TestICServerStats(t *testing.T) { } cli.client = httpClient - _, err = cli.ServerStats() + _, err = cli.ServerStats(context.Background()) assert.Nil(t, err) } } @@ -687,7 +688,7 @@ func TestICErrorResponses(t *testing.T) { url string htusername string htpassword string - id string + id int } type response struct { code int @@ -705,7 +706,7 @@ func TestICErrorResponses(t *testing.T) { url: "http://test.com", htusername: "user", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusOK, @@ -717,7 +718,7 @@ func TestICErrorResponses(t *testing.T) { url: "http://test.com", htusername: "user", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusNotFound, @@ -733,7 +734,7 @@ func TestICErrorResponses(t *testing.T) { url: "http://test.com", htusername: "user", htpassword: "password", - id: "1", + id: 1, }, response: response{ code: http.StatusInternalServerError, @@ -776,7 +777,7 @@ func TestICErrorResponses(t *testing.T) { cli.client = httpClient - err = cli.ExpirePassword(tc.request.id) + err = cli.ExpirePassword(context.Background(), tc.request.id) if tc.errorFields == nil { assert.NoError(t, err) } else {