broker.go
package main
import (
"code.cloudfoundry.org/lager"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/cloudfoundry-community/go-cfclient"
"github.com/pivotal-cf/brokerapi"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
type BindOptions struct {
RedirectURI []string `json:"redirect_uri"`
Scopes []string `json:"scopes"`
}
var (
clientAccountGUID = "6b508bb8-2af7-4a75-9efd-7b76a01d705d"
userAccountGUID = "964bd86d-72fa-4852-957f-e4cd802de34b"
deployerGUID = "074e652b-b77b-4ac3-8d5b-52144486b1a3"
auditorGUID = "dc3a6d48-9622-434a-b418-1d920193b575"
)
var (
defaultScopes = []string{"openid"}
allowedScopes = map[string]bool{
"openid": true,
}
)
type DeployerAccountBroker struct {
uaaClient AuthClient
cfClient PAASClient
generatePassword PasswordGenerator
logger lager.Logger
config Config
}
func (b *DeployerAccountBroker) Services(context context.Context) []brokerapi.Service {
var services []brokerapi.Service
pwd, _ := os.Getwd()
buf, err := ioutil.ReadFile(filepath.Join(pwd, "config.json"))
if err != nil {
b.logger.Error("services", err)
return []brokerapi.Service{}
}
err = json.Unmarshal(buf, &services)
if err != nil {
return []brokerapi.Service{}
}
return services
}
func (b *DeployerAccountBroker) Provision(
context context.Context,
instanceID string,
details brokerapi.ProvisionDetails,
asyncAllowed bool,
) (brokerapi.ProvisionedServiceSpec, error) {
return brokerapi.ProvisionedServiceSpec{}, nil
}
func (b *DeployerAccountBroker) Deprovision(
context context.Context,
instanceID string,
details brokerapi.DeprovisionDetails,
asyncAllowed bool,
) (brokerapi.DeprovisionServiceSpec, error) {
// Handle instances created before credential management was moved to bind and unbind
switch details.ServiceID {
case clientAccountGUID:
if err := b.uaaClient.DeleteClient(instanceID); err != nil && !strings.Contains(err.Error(), "404") {
return brokerapi.DeprovisionServiceSpec{}, err
}
case userAccountGUID:
user, err := b.uaaClient.GetUser(instanceID)
if err != nil {
if strings.Contains(err.Error(), "got 0") {
return brokerapi.DeprovisionServiceSpec{}, nil
}
return brokerapi.DeprovisionServiceSpec{}, err
}
err = b.cfClient.DeleteUser(user.ID)
if err != nil {
return brokerapi.DeprovisionServiceSpec{}, err
}
err = b.uaaClient.DeleteUser(user.ID)
if err != nil {
return brokerapi.DeprovisionServiceSpec{}, err
}
default:
return brokerapi.DeprovisionServiceSpec{}, fmt.Errorf("Service ID %s not found", details.ServiceID)
}
return brokerapi.DeprovisionServiceSpec{}, nil
}
func (b *DeployerAccountBroker) Bind(
context context.Context,
instanceID, bindingID string,
details brokerapi.BindDetails,
) (brokerapi.Binding, error) {
password := b.generatePassword(b.config.PasswordLength)
switch details.ServiceID {
case clientAccountGUID:
var opts BindOptions
if len(details.RawParameters) == 0 {
return brokerapi.Binding{}, errors.New(`Must pass JSON configuration with field "redirect_uri"`)
}
if err := json.Unmarshal(details.RawParameters, &opts); err != nil {
return brokerapi.Binding{}, err
}
if len(opts.RedirectURI) == 0 {
return brokerapi.Binding{}, errors.New(`Must pass field "redirect_uri"`)
}
if _, err := b.provisionClient(bindingID, password, opts.RedirectURI, opts.Scopes); err != nil {
return brokerapi.Binding{}, err
}
return brokerapi.Binding{
Credentials: map[string]string{
"client_id": bindingID,
"client_secret": password,
},
}, nil
case userAccountGUID:
instance, err := b.cfClient.ServiceInstanceByGuid(instanceID)
if err != nil {
return brokerapi.Binding{}, err
}
space, err := b.cfClient.GetSpaceByGuid(instance.SpaceGuid)
if err != nil {
return brokerapi.Binding{}, err
}
user, err := b.provisionUser(bindingID, password)
if err != nil {
return brokerapi.Binding{}, err
}
_, err = b.cfClient.CreateUser(cfclient.UserRequest{Guid: user.ID})
if err != nil {
return brokerapi.Binding{}, err
}
_, err = b.cfClient.AssociateOrgUserByUsername(space.OrganizationGuid, user.UserName)
if err != nil {
return brokerapi.Binding{}, err
}
switch details.PlanID {
case deployerGUID:
_, err = b.cfClient.AssociateSpaceDeveloperByUsername(instance.SpaceGuid, user.UserName)
if err != nil {
return brokerapi.Binding{}, err
}
case auditorGUID:
_, err = b.cfClient.AssociateSpaceAuditorByUsername(instance.SpaceGuid, user.UserName)
if err != nil {
return brokerapi.Binding{}, err
}
}
return brokerapi.Binding{
Credentials: map[string]string{
"username": bindingID,
"password": password,
},
}, nil
default:
return brokerapi.Binding{}, fmt.Errorf("Service ID %s not found", details.ServiceID)
}
return brokerapi.Binding{}, nil
}
func (b *DeployerAccountBroker) Unbind(
context context.Context,
instanceID, bindingID string,
details brokerapi.UnbindDetails,
) error {
switch details.ServiceID {
case clientAccountGUID:
if err := b.uaaClient.DeleteClient(bindingID); err != nil {
return err
}
case userAccountGUID:
user, err := b.uaaClient.GetUser(bindingID)
if err != nil {
return err
}
err = b.cfClient.DeleteUser(user.ID)
if err != nil {
return err
}
err = b.uaaClient.DeleteUser(user.ID)
if err != nil {
return err
}
default:
return fmt.Errorf("Service ID %s not found", details.ServiceID)
}
return nil
}
func (b *DeployerAccountBroker) Update(context context.Context, instanceID string, details brokerapi.UpdateDetails, asyncAllowed bool) (brokerapi.UpdateServiceSpec, error) {
return brokerapi.UpdateServiceSpec{}, errors.New("Broker does not support update")
}
func (b *DeployerAccountBroker) LastOperation(context context.Context, instanceID, operationData string) (brokerapi.LastOperation, error) {
return brokerapi.LastOperation{}, errors.New("Broker does not support last operation")
}
func (b *DeployerAccountBroker) provisionClient(clientID, clientSecret string, redirectURI []string, scopes []string) (Client, error) {
if len(scopes) == 0 {
scopes = defaultScopes
}
forbiddenScopes := []string{}
for _, scope := range scopes {
if _, ok := allowedScopes[scope]; !ok {
forbiddenScopes = append(forbiddenScopes, scope)
}
}
if len(forbiddenScopes) > 0 {
return Client{}, fmt.Errorf("Scope(s) not permitted: %s", strings.Join(forbiddenScopes, ", "))
}
return b.uaaClient.CreateClient(Client{
ID: clientID,
AuthorizedGrantTypes: []string{"authorization_code", "refresh_token"},
Scope: scopes,
RedirectURI: redirectURI,
ClientSecret: clientSecret,
AccessTokenValidity: b.config.AccessTokenValidity,
RefreshTokenValidity: b.config.RefreshTokenValidity,
})
}
func (b *DeployerAccountBroker) provisionUser(userID, password string) (User, error) {
user := User{
UserName: userID,
Password: password,
Emails: []Email{{
Value: b.config.EmailAddress,
Primary: true,
}},
}
return b.uaaClient.CreateUser(user)
}