Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanmoran committed Mar 17, 2015
1 parent 021e77b commit e89237b
Show file tree
Hide file tree
Showing 35 changed files with 2,033 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# A Client Library for UAA, written in Go
13 changes: 13 additions & 0 deletions acceptance/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package acceptance

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestAcceptanceSuite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Acceptance Suite")
}
104 changes: 104 additions & 0 deletions acceptance/passwords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package acceptance

import (
"fmt"
"os"
"os/exec"
"regexp"

"github.com/pivotal-cf-experimental/warrant"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Passwords", func() {
var (
client warrant.Client
token string
)

BeforeEach(func() {
token = os.Getenv("UAA_TOKEN")

client = warrant.NewClient(warrant.Config{
Host: os.Getenv("UAA_HOST"),
SkipVerifySSL: true,
})
})

It("allows a user password to be set/updated", func() {
var (
user warrant.User
userToken string
)

By("creating a new user", func() {
var err error
user, err = client.Users.Create("user-name", "user@example.com", token)
Expect(err).NotTo(HaveOccurred())
})

By("setting the user password using a valid client", func() {
err := client.Users.SetPassword(user.ID, "password", token)
Expect(err).NotTo(HaveOccurred())
})

By("retrieving the user token using the new password", func() {
// TODO: replace with implementation that does not call out to UAAC
cmd := exec.Command("uaac", "token", "get", user.UserName, "password")
output, err := cmd.Output()
Expect(err).NotTo(HaveOccurred())
Expect(output).To(ContainSubstring("Successfully fetched token via implicit (with posted credentials) grant."))

cmd = exec.Command("uaac", "token", "decode")
output, err = cmd.Output()
Expect(err).NotTo(HaveOccurred())
Expect(output).To(ContainSubstring(fmt.Sprintf("user_id: %s", user.ID)))

cmd = exec.Command("uaac", "context")
output, err = cmd.Output()
Expect(err).NotTo(HaveOccurred())
matches := regexp.MustCompile(`access_token: (.*)\n`).FindStringSubmatch(string(output))
Expect(matches).To(HaveLen(2))

userToken = matches[1]
Expect(userToken).NotTo(BeEmpty())
})

By("changing a user's own password", func() {
err := client.Users.ChangePassword(user.ID, "password", "new-password", userToken)
Expect(err).NotTo(HaveOccurred())
})

By("retrieving the user token using the new password", func() {
// TODO: replace with implementation that does not call out to UAAC
cmd := exec.Command("uaac", "token", "get", user.UserName, "new-password")
output, err := cmd.Output()
Expect(err).NotTo(HaveOccurred())
Expect(output).To(ContainSubstring("Successfully fetched token via implicit (with posted credentials) grant."))

cmd = exec.Command("uaac", "token", "decode")
output, err = cmd.Output()
Expect(err).NotTo(HaveOccurred())
Expect(output).To(ContainSubstring(fmt.Sprintf("user_id: %s", user.ID)))

cmd = exec.Command("uaac", "context")
output, err = cmd.Output()
Expect(err).NotTo(HaveOccurred())
matches := regexp.MustCompile(`access_token: (.*)\n`).FindStringSubmatch(string(output))
Expect(matches).To(HaveLen(2))

userToken = matches[1]
Expect(userToken).NotTo(BeEmpty())
})

By("deleting the user", func() {
err := client.Users.Delete(user.ID, token)
Expect(err).NotTo(HaveOccurred())

_, err = client.Users.Get(user.ID, token)
Expect(err).To(BeAssignableToTypeOf(warrant.NotFoundError{}))
})
})
})
88 changes: 88 additions & 0 deletions acceptance/user_lifecycle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package acceptance

import (
"os"
"time"

"github.com/pivotal-cf-experimental/warrant"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("User Lifecycle", func() {
var (
client warrant.Client
token string
)

BeforeEach(func() {
token = os.Getenv("UAA_TOKEN")

client = warrant.NewClient(warrant.Config{
Host: os.Getenv("UAA_HOST"),
SkipVerifySSL: true,
})
})

It("creates, retrieves, and deletes a user", func() {
var user warrant.User

By("creating a new user", func() {
var err error
user, err = client.Users.Create("user-name", "user@example.com", token)
Expect(err).NotTo(HaveOccurred())
Expect(user.UserName).To(Equal("user-name"))
Expect(user.Emails).To(ConsistOf([]string{"user@example.com"}))
Expect(user.CreatedAt).To(BeTemporally("~", time.Now().UTC(), 10*time.Minute)) // TODO: this is weird, but server time could be divergent from local time
Expect(user.UpdatedAt).To(BeTemporally("~", time.Now().UTC(), 10*time.Minute))
Expect(user.Version).To(Equal(0))
Expect(user.Emails).To(ConsistOf([]string{"user@example.com"}))
Expect(user.Active).To(BeTrue())
Expect(user.Verified).To(BeFalse())
Expect(user.Origin).To(Equal("uaa"))
//Expect(user.Groups).To(ConsistOf([]warrant.Group{})) TODO: finish up groups implementation
})

By("finding the user", func() {
fetchedUser, err := client.Users.Get(user.ID, token)
Expect(err).NotTo(HaveOccurred())
Expect(fetchedUser).To(Equal(user))
})

By("updating the user", func() {
updatedUser, err := client.Users.Update(user, token)
Expect(err).NotTo(HaveOccurred())

fetchedUser, err := client.Users.Get(user.ID, token)
Expect(err).NotTo(HaveOccurred())
Expect(fetchedUser).To(Equal(updatedUser))
})

By("deleting the user", func() {
err := client.Users.Delete(user.ID, token)
Expect(err).NotTo(HaveOccurred())

_, err = client.Users.Get(user.ID, token)
Expect(err).To(BeAssignableToTypeOf(warrant.NotFoundError{}))
})
})

It("does not allow a user to be created without an email address", func() {
_, err := client.Users.Create("invalid-email-user", "", token)
Expect(err).To(BeAssignableToTypeOf(warrant.UnexpectedStatusError{}))
})

It("does not allow non-existant users to be updated", func() {
user, err := client.Users.Create("user-name", "user@example.com", token)
Expect(err).NotTo(HaveOccurred())

originalUserID := user.ID
user.ID = "non-existant-user-guid"
_, err = client.Users.Update(user, token)
Expect(err).To(BeAssignableToTypeOf(warrant.NotFoundError{}))

err = client.Users.Delete(originalUserID, token)
Expect(err).NotTo(HaveOccurred())
})
})
24 changes: 24 additions & 0 deletions bin/acceptance
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash -e

DIR=`cd $(dirname $0)/.. && pwd`
if [[ -z "$UAA_HOST" ]]; then
echo "UAA_HOST is a required env var"
exit 1
fi

if [[ -z "$UAA_ADMIN_CLIENT" ]]; then
echo "UAA_ADMIN_CLIENT is a required env var"
exit 1
fi

if [[ -z "$UAA_ADMIN_SECRET" ]]; then
echo "UAA_ADMIN_SECRET is a required env var"
exit 1
fi

uaac target $UAA_HOST
uaac token client get $UAA_ADMIN_CLIENT -s $UAA_ADMIN_SECRET

export UAA_TOKEN=`uaac context | grep access_token | awk '{ print $2 }'`

ginkgo -r $DIR/acceptance
6 changes: 6 additions & 0 deletions bin/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash -e

DIR=$(dirname $0)

$DIR/unit
$DIR/acceptance
5 changes: 5 additions & 0 deletions bin/unit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash -e

DIR=`cd $(dirname $0)/.. && pwd`

ginkgo -r -skipPackage=acceptance $DIR
123 changes: 123 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package warrant

import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"

"github.com/pivotal-cf-experimental/warrant/internal/network"
)

type Config struct {
Host string
SkipVerifySSL bool
}

type requestArguments struct {
Method string
Path string
Token string
IfMatch string
Body interface{}
AcceptableStatusCodes []int
}

type Client struct {
config Config
Users UsersService
}

func NewClient(config Config) Client {
return Client{
config: config,
Users: NewUsersService(config),
}
}

func (c Client) makeRequest(requestArgs requestArguments) (int, []byte, error) {
if requestArgs.AcceptableStatusCodes == nil {
panic("acceptable status codes for this request were not set")
}

var bodyReader io.Reader
if requestArgs.Body != nil {
requestBody, err := json.Marshal(requestArgs.Body)
if err != nil {
return 0, []byte{}, newRequestBodyMarshalError(err)
}
bodyReader = bytes.NewReader(requestBody)
}

requestURL := c.config.Host + requestArgs.Path
request, err := http.NewRequest(requestArgs.Method, requestURL, bodyReader)
if err != nil {
return 0, []byte{}, newRequestConfigurationError(err)
}
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", requestArgs.Token))

if requestArgs.Body != nil {
request.Header.Set("Content-Type", "application/json")
}
if requestArgs.IfMatch != "" {
request.Header.Set("If-Match", requestArgs.IfMatch)
}

c.printRequest(request)

response, err := network.GetClient(network.Config{
SkipVerifySSL: c.config.SkipVerifySSL,
}).Do(request)
if err != nil {
return 0, []byte{}, newRequestHTTPError(err)
}

responseBody, err := ioutil.ReadAll(response.Body)
if err != nil {
return 0, []byte{}, newResponseReadError(err)
}

c.printResponse(response.StatusCode, responseBody)

if response.StatusCode == http.StatusNotFound {
return response.StatusCode, responseBody, newNotFoundError(responseBody)
}

if response.StatusCode == http.StatusUnauthorized {
return response.StatusCode, responseBody, newUnauthorizedError(responseBody)
}

for _, acceptableCode := range requestArgs.AcceptableStatusCodes {
if response.StatusCode == acceptableCode {
return response.StatusCode, responseBody, nil
}
}

return response.StatusCode, responseBody, newUnexpectedStatusError(response.StatusCode, responseBody)
}

func (c Client) printRequest(request *http.Request) {
if os.Getenv("TRACE") != "" {
bodyCopy := bytes.NewBuffer([]byte{})
if request.Body != nil {
body := bytes.NewBuffer([]byte{})
_, err := io.Copy(io.MultiWriter(body, bodyCopy), request.Body)
if err != nil {
panic(err)
}

request.Body = ioutil.NopCloser(body)
}

fmt.Printf("REQUEST: %s %s %s %v\n", request.Method, request.URL, bodyCopy.String(), request.Header)
}
}

func (c Client) printResponse(status int, body []byte) {
if os.Getenv("TRACE") != "" {
fmt.Printf("RESPONSE: %d %s\n", status, body)
}
}
Loading

0 comments on commit e89237b

Please sign in to comment.