Skip to content

Commit

Permalink
Merge pull request #193 from bonitoo-io/feat/sign_in
Browse files Browse the repository at this point in the history
feat: Added SignIn and SignOut
  • Loading branch information
vlastahajek committed Aug 21, 2020
2 parents 68f42dd + fd129bc commit 324827c
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -1,4 +1,7 @@
## 2.1.0 [in progress]
### Features
1. [#193](https://github.com/influxdata/influxdb-client-go/pull/193) Added authentication using username and password. See `UsersAPI.SignIn()` and `UsersAPI.SignOut()`

### Bug fixes
1. [#191](https://github.com/influxdata/influxdb-client-go/pull/191) Fixed QueryTableResult.Next() failed to parse boolean datatype.
1. [#192](https://github.com/influxdata/influxdb-client-go/pull/192) Client.Close() closes idle connections of internally created HTTP client
Expand Down
27 changes: 27 additions & 0 deletions api/examples_test.go
Expand Up @@ -325,6 +325,33 @@ func ExampleUsersAPI() {
client.Close()
}

func ExampleUsersAPI_signInOut() {
// Create a new client using an InfluxDB server base URL and empty token
client := influxdb2.NewClient("http://localhost:9999", "")
// Always close client at the end
defer client.Close()

ctx := context.Background()

// The first call must be signIn
err := client.UsersAPI().SignIn(ctx, "username", "password")
if err != nil {
panic(err)
}

// Perform some authorized operations
err = client.WriteAPIBlocking("my-org", "my-bucket").WriteRecord(ctx, "test,a=rock,b=local f=1.2,i=-5i")
if err != nil {
panic(err)
}

// Sign out at the end
err = client.UsersAPI().SignOut(ctx)
if err != nil {
panic(err)
}
}

func ExampleLabelsAPI() {
// Create a new client using an InfluxDB server base URL and an authentication token
client := influxdb2.NewClient("http://localhost:9999", "my-token")
Expand Down
1 change: 1 addition & 0 deletions api/http/options.go
Expand Up @@ -50,6 +50,7 @@ func (o *Options) HTTPClient() *http.Client {
//
// Setting the HTTPClient will cause the other HTTP options
// to be ignored.
// In case of UsersAPI.SignIn() is used, HTTPClient.Jar will be used for storing session cookie.
func (o *Options) SetHTTPClient(c *http.Client) *Options {
o.httpClient = c
o.ownClient = false
Expand Down
4 changes: 3 additions & 1 deletion api/http/service.go
Expand Up @@ -124,7 +124,9 @@ func (s *service) DoHTTPRequest(req *http.Request, requestCallback RequestCallba

func (s *service) DoHTTPRequestWithResponse(req *http.Request, requestCallback RequestCallback) (*http.Response, error) {
log.Infof("HTTP %s req to %s", req.Method, req.URL.String())
req.Header.Set("Authorization", s.authorization)
if len(s.authorization) > 0 {
req.Header.Set("Authorization", s.authorization)
}
req.Header.Set("User-Agent", http2.UserAgent)
if requestCallback != nil {
requestCallback(req)
Expand Down
88 changes: 82 additions & 6 deletions api/users.go
Expand Up @@ -6,8 +6,15 @@ package api

import (
"context"
"encoding/base64"
"fmt"
nethttp "net/http"
"net/http/cookiejar"
"sync"

"github.com/influxdata/influxdb-client-go/v2/api/http"
"github.com/influxdata/influxdb-client-go/v2/domain"
"golang.org/x/net/publicsuffix"
)

// UsersAPI provides methods for managing users in a InfluxDB server
Expand Down Expand Up @@ -35,16 +42,26 @@ type UsersAPI interface {
// Me returns actual user
Me(ctx context.Context) (*domain.User, error)
// MeUpdatePassword set password of actual user
MeUpdatePassword(ctx context.Context, password string) error
MeUpdatePassword(ctx context.Context, oldPassword, newPassword string) error
// SignIn exchanges username and password credentials to establish an authenticated session with the InfluxDB server. The Client's authentication token is then ignored, it can be empty.
SignIn(ctx context.Context, username, password string) error
// SignOut signs out previously signed in user
SignOut(ctx context.Context) error
}

type usersAPI struct {
apiClient *domain.ClientWithResponses
apiClient *domain.ClientWithResponses
httpService http.Service
httpClient *nethttp.Client
deleteCookieJar bool
lock sync.Mutex
}

func NewUsersAPI(apiClient *domain.ClientWithResponses) UsersAPI {
func NewUsersAPI(apiClient *domain.ClientWithResponses, httpService http.Service, httpClient *nethttp.Client) UsersAPI {
return &usersAPI{
apiClient: apiClient,
apiClient: apiClient,
httpService: httpService,
httpClient: httpClient,
}
}

Expand Down Expand Up @@ -164,9 +181,19 @@ func (u *usersAPI) Me(ctx context.Context) (*domain.User, error) {
return response.JSON200, nil
}

func (u *usersAPI) MeUpdatePassword(ctx context.Context, password string) error {
func (u *usersAPI) MeUpdatePassword(ctx context.Context, oldPassword, newPassword string) error {
u.lock.Lock()
defer u.lock.Unlock()
me, err := u.Me(ctx)
if err != nil {
return err
}
creds := base64.StdEncoding.EncodeToString([]byte(me.Name + ":" + oldPassword))
auth := u.httpService.Authorization()
defer u.httpService.SetAuthorization(auth)
u.httpService.SetAuthorization("Basic " + creds)
params := &domain.PutMePasswordParams{}
body := &domain.PasswordResetBody{Password: password}
body := &domain.PasswordResetBody{Password: newPassword}
response, err := u.apiClient.PutMePasswordWithResponse(ctx, params, domain.PutMePasswordJSONRequestBody(*body))
if err != nil {
return err
Expand All @@ -176,3 +203,52 @@ func (u *usersAPI) MeUpdatePassword(ctx context.Context, password string) error
}
return nil
}

func (u *usersAPI) SignIn(ctx context.Context, username, password string) error {
u.lock.Lock()
defer u.lock.Unlock()
if u.httpClient.Jar == nil {
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
return err
}
u.httpClient.Jar = jar
u.deleteCookieJar = true
}
creds := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
u.httpService.SetAuthorization("Basic " + creds)
defer u.httpService.SetAuthorization("")
resp, err := u.apiClient.PostSigninWithResponse(ctx, &domain.PostSigninParams{})
if err != nil {
return err
}
if resp.JSONDefault != nil {
return domain.DomainErrorToError(resp.JSONDefault, resp.StatusCode())
}
if resp.JSON401 != nil {
return domain.DomainErrorToError(resp.JSON401, resp.StatusCode())
}
if resp.JSON403 != nil {
return domain.DomainErrorToError(resp.JSON403, resp.StatusCode())
}
return nil
}

func (u *usersAPI) SignOut(ctx context.Context) error {
u.lock.Lock()
defer u.lock.Unlock()
resp, err := u.apiClient.PostSignoutWithResponse(ctx, &domain.PostSignoutParams{})
if err != nil {
return err
}
if resp.JSONDefault != nil {
return domain.DomainErrorToError(resp.JSONDefault, resp.StatusCode())
}
if resp.JSON401 != nil {
return domain.DomainErrorToError(resp.JSON401, resp.StatusCode())
}
if u.deleteCookieJar {
u.httpClient.Jar = nil
}
return nil
}
94 changes: 90 additions & 4 deletions api/users_e2e_test.go
Expand Up @@ -75,9 +75,12 @@ func TestUsersAPI(t *testing.T) {
require.NotNil(t, users)
assert.Len(t, *users, 1)

// this fails now
err = usersAPI.MeUpdatePassword(ctx, "new-password")
assert.NotNil(t, err)
// it fails, https://github.com/influxdata/influxdb/pull/15981
//err = usersAPI.MeUpdatePassword(ctx, "my-password", "my-new-password")
//assert.Nil(t, err)

//err = usersAPI.MeUpdatePassword(ctx, "my-new-password", "my-password")
//assert.Nil(t, err)
}

func TestUsersAPI_failing(t *testing.T) {
Expand Down Expand Up @@ -136,6 +139,89 @@ func TestUsersAPI_requestFailing(t *testing.T) {
_, err = usersAPI.Me(ctx)
assert.NotNil(t, err)

err = usersAPI.MeUpdatePassword(ctx, "pass")
err = usersAPI.MeUpdatePassword(ctx, "my-password", "my-new-password")
assert.NotNil(t, err)

err = usersAPI.SignIn(ctx, "user", "my-password")
assert.NotNil(t, err)

err = usersAPI.SignOut(ctx)
assert.NotNil(t, err)
}

func TestSignInOut(t *testing.T) {
ctx := context.Background()
client := influxdb2.NewClient("http://localhost:9999", "")

usersAPI := client.UsersAPI()

err := usersAPI.SignIn(ctx, "my-user", "my-password")
require.Nil(t, err)

// try authorized calls
orgs, err := client.OrganizationsAPI().GetOrganizations(ctx)
assert.Nil(t, err)
assert.NotNil(t, orgs)

// try authorized calls
buckets, err := client.BucketsAPI().GetBuckets(ctx)
assert.Nil(t, err)
assert.NotNil(t, buckets)

// try authorized calls
err = client.WriteAPIBlocking("my-org", "my-bucket").WriteRecord(ctx, "test,a=rock,b=local f=1.2,i=-5i")
assert.Nil(t, err)

res, err := client.QueryAPI("my-org").QueryRaw(context.Background(), `from(bucket:"my-bucket")|> range(start: -24h) |> filter(fn: (r) => r._measurement == "test")`, influxdb2.DefaultDialect())
assert.Nil(t, err)
assert.NotNil(t, res)

err = usersAPI.SignOut(ctx)
assert.Nil(t, err)

// unauthorized signout
err = usersAPI.SignOut(ctx)
assert.NotNil(t, err)

// Unauthorized call
_, err = client.OrganizationsAPI().GetOrganizations(ctx)
assert.NotNil(t, err)

// test wrong credentials
err = usersAPI.SignIn(ctx, "my-user", "password")
assert.NotNil(t, err)

client.HTTPService().SetAuthorization("Token my-token")

user, err := usersAPI.CreateUserWithName(ctx, "user-01")
require.Nil(t, err)
require.NotNil(t, user)

// 2nd client to use for new user auth
client2 := influxdb2.NewClient("http://localhost:9999", "")

err = usersAPI.UpdateUserPassword(ctx, user, "123password")
assert.Nil(t, err)

err = client2.UsersAPI().SignIn(ctx, "user-01", "123password")
assert.Nil(t, err)

err = client2.UsersAPI().SignOut(ctx)
assert.Nil(t, err)

status := domain.UserStatusInactive
user.Status = &status
u, err := usersAPI.UpdateUser(ctx, user)
assert.Nil(t, err)
assert.NotNil(t, u)

// log in inactive user,
//err = client2.SignIn(ctx, "user-01", "123password")
//assert.NotNil(t, err)

err = usersAPI.DeleteUser(ctx, user)
assert.Nil(t, err)

client.Close()
client2.Close()
}
8 changes: 6 additions & 2 deletions client.go
Expand Up @@ -98,7 +98,11 @@ func NewClientWithOptions(serverURL string, authToken string, options *Options)
// For subsequent path parts concatenation, url has to end with '/'
normServerURL = serverURL + "/"
}
service := http.NewService(normServerURL, "Token "+authToken, options.httpOptions)
authorization := ""
if len(authToken) > 0 {
authorization = "Token " + authToken
}
service := http.NewService(normServerURL, authorization, options.httpOptions)
client := &clientImpl{
serverURL: serverURL,
options: options,
Expand Down Expand Up @@ -245,7 +249,7 @@ func (c *clientImpl) UsersAPI() api.UsersAPI {
c.lock.Lock()
defer c.lock.Unlock()
if c.usersAPI == nil {
c.usersAPI = api.NewUsersAPI(c.apiClient)
c.usersAPI = api.NewUsersAPI(c.apiClient, c.httpService, c.options.HTTPClient())
}
return c.usersAPI
}
Expand Down
19 changes: 17 additions & 2 deletions examples_test.go
Expand Up @@ -12,7 +12,7 @@ func ExampleClient_newClient() {
// Create a new client using an InfluxDB server base URL and an authentication token
client := influxdb2.NewClient("http://localhost:9999", "my-token")

// always close client at the end
// Always close client at the end
defer client.Close()
}

Expand All @@ -22,18 +22,25 @@ func ExampleClient_newClientWithOptions() {
client := influxdb2.NewClientWithOptions("http://localhost:9999", "my-token",
influxdb2.DefaultOptions().SetBatchSize(20))

// always close client at the end
// Always close client at the end
defer client.Close()
}

func ExampleClient_customServerAPICall() {
// Create a new client using an InfluxDB server base URL and empty token
client := influxdb2.NewClient("http://localhost:9999", "my-token")
// Always close client at the end
defer client.Close()
// Get generated client for server API calls
apiClient := domain.NewClientWithResponses(client.HTTPService())
// Get an organization that will own task
org, err := client.OrganizationsAPI().FindOrganizationByName(context.Background(), "my-org")
if err != nil {
//return err
panic(err)
}

// Basic task properties
taskDescription := "Example task"
taskFlux := `option task = {
name: "My task",
Expand All @@ -42,20 +49,28 @@ func ExampleClient_customServerAPICall() {
from(bucket:"my-bucket") |> range(start: -1m) |> last()`
taskStatus := domain.TaskStatusTypeActive

// Create TaskCreateRequest object
taskRequest := domain.TaskCreateRequest{
Org: &org.Name,
OrgID: org.Id,
Description: &taskDescription,
Flux: taskFlux,
Status: &taskStatus,
}

// Issue an API call
resp, err := apiClient.PostTasksWithResponse(context.Background(), &domain.PostTasksParams{}, domain.PostTasksJSONRequestBody(taskRequest))
if err != nil {
panic(err)
}

// Always check generated response errors
if resp.JSONDefault != nil {
panic(resp.JSONDefault.Message)
}

// Use API call result
task := resp.JSON201
fmt.Println("Created task: ", task.Name)
}
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -7,5 +7,6 @@ require (
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.4.0 // test dependency
golang.org/x/net v0.0.0-20191112182307-2180aed22343
gopkg.in/yaml.v2 v2.2.5
)
1 change: 1 addition & 0 deletions options.go
Expand Up @@ -144,6 +144,7 @@ func (o *Options) HTTPClient() *nethttp.Client {
//
// Setting the HTTPClient will cause the other HTTP options
// to be ignored.
// In case of UsersAPI.SignIn() is used, HTTPClient.Jar will be used for storing session cookie.
func (o *Options) SetHTTPClient(c *nethttp.Client) *Options {
o.httpOptions.SetHTTPClient(c)
return o
Expand Down

0 comments on commit 324827c

Please sign in to comment.