Skip to content

Commit

Permalink
Merge pull request #29 from k-capehart/add-client-credential-flow
Browse files Browse the repository at this point in the history
add client credentials flow to authentication
  • Loading branch information
k-capehart authored May 6, 2024
2 parents 0c4be53 + b13e235 commit 5d94e55
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 24 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,23 @@ type Creds struct {

Returns a new Salesforce instance given a user's credentials.
- `creds`: a struct containing the necessary credentials to authenticate into a Salesforce org
- [Creating a Connected App in Salesforce](https://help.salesforce.com/s/articleView?id=sf.connected_app_create.htm&type=5)

Client Credentials Flow
- [Client Credentials Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_client_credentials_flow.htm&type=5)
```go
sf, sfErr := salesforce.Init(salesforce.Creds{
Domain: DOMAIN,
ConsumerKey: CONSUMER_KEY,
ConsumerSecret: CONSUMER_SECRET,
})
if sfErr != nil {
panic(sfErr)
}
```

Username-Password Flow
- [Create a Connected App in your Salesforce org](https://help.salesforce.com/s/articleView?id=sf.connected_app_create.htm&type=5)
- [Username-Password Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_username_password_flow.htm&type=5)
```go
sf, err := salesforce.Init(salesforce.Creds{
Domain: DOMAIN,
Expand Down
47 changes: 36 additions & 11 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type authentication struct {
AccessToken string `json:"access_token"`
InstanceUrl string `json:"instance_url"`
Id string `json:"id"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
IssuedAt string `json:"issued_at"`
Signature string `json:"signature"`
}
Expand All @@ -33,17 +35,8 @@ func validateAuth(sf Salesforce) error {
return nil
}

func loginPassword(domain string, username string, password string, securityToken string, consumerKey string, consumerSecret string) (*authentication, error) {
payload := url.Values{
"grant_type": {"password"},
"client_id": {consumerKey},
"client_secret": {consumerSecret},
"username": {username},
"password": {password + securityToken},
}
endpoint := "/services/oauth2/token"
body := strings.NewReader(payload.Encode())
resp, err := http.Post(domain+endpoint, "application/x-www-form-urlencoded", body)
func doAuth(url string, body *strings.Reader) (*authentication, error) {
resp, err := http.Post(url, "application/x-www-form-urlencoded", body)
if err != nil {
return nil, err
}
Expand All @@ -65,3 +58,35 @@ func loginPassword(domain string, username string, password string, securityToke
defer resp.Body.Close()
return auth, nil
}

func usernamePasswordFlow(domain string, username string, password string, securityToken string, consumerKey string, consumerSecret string) (*authentication, error) {
payload := url.Values{
"grant_type": {"password"},
"client_id": {consumerKey},
"client_secret": {consumerSecret},
"username": {username},
"password": {password + securityToken},
}
endpoint := "/services/oauth2/token"
body := strings.NewReader(payload.Encode())
auth, err := doAuth(domain+endpoint, body)
if err != nil {
return nil, err
}
return auth, nil
}

func clientCredentialsFlow(domain string, consumerKey string, consumerSecret string) (*authentication, error) {
payload := url.Values{
"grant_type": {"client_credentials"},
"client_id": {consumerKey},
"client_secret": {consumerSecret},
}
endpoint := "/services/oauth2/token"
body := strings.NewReader(payload.Encode())
auth, err := doAuth(domain+endpoint, body)
if err != nil {
return nil, err
}
return auth, nil
}
72 changes: 67 additions & 5 deletions auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func Test_validateAuth(t *testing.T) {
}
}

func Test_loginPassword(t *testing.T) {
func Test_usernamePasswordFlow(t *testing.T) {
auth := authentication{
AccessToken: "1234",
InstanceUrl: "example.com",
Expand All @@ -53,10 +53,10 @@ func Test_loginPassword(t *testing.T) {
server, _ := setupTestServer(auth, http.StatusOK)
defer server.Close()

badserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
badServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
}))
defer badserver.Close()
defer badServer.Close()

type args struct {
domain string
Expand Down Expand Up @@ -88,7 +88,7 @@ func Test_loginPassword(t *testing.T) {
{
name: "authentication_fail",
args: args{
domain: badserver.URL,
domain: badServer.URL,
username: "u",
password: "p",
securityToken: "t",
Expand All @@ -101,7 +101,7 @@ func Test_loginPassword(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := loginPassword(tt.args.domain, tt.args.username, tt.args.password, tt.args.securityToken, tt.args.consumerKey, tt.args.consumerSecret)
got, err := usernamePasswordFlow(tt.args.domain, tt.args.username, tt.args.password, tt.args.securityToken, tt.args.consumerKey, tt.args.consumerSecret)
if (err != nil) != tt.wantErr {
t.Errorf("loginPassword() error = %v, wantErr %v", err, tt.wantErr)
return
Expand All @@ -112,3 +112,65 @@ func Test_loginPassword(t *testing.T) {
})
}
}

func Test_clientCredentialsFlow(t *testing.T) {
auth := authentication{
AccessToken: "1234",
InstanceUrl: "example.com",
Id: "123abc",
IssuedAt: "01/01/1970",
Signature: "signed",
}
server, _ := setupTestServer(auth, http.StatusOK)
defer server.Close()

badServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
}))
defer badServer.Close()

type args struct {
domain string
consumerKey string
consumerSecret string
}
tests := []struct {
name string
args args
want *authentication
wantErr bool
}{
{
name: "authentication_success",
args: args{
domain: server.URL,
consumerKey: "key",
consumerSecret: "secret",
},
want: &auth,
wantErr: false,
},
{
name: "authentication_fail",
args: args{
domain: badServer.URL,
consumerKey: "key",
consumerSecret: "secret",
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := clientCredentialsFlow(tt.args.domain, tt.args.consumerKey, tt.args.consumerSecret)
if (err != nil) != tt.wantErr {
t.Errorf("clientCredentialsFlow() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("clientCredentialsFlow() = %v, want %v", got, tt.want)
}
})
}
}
22 changes: 15 additions & 7 deletions salesforce.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,18 @@ func processSalesforceResponse(resp http.Response) error {
func Init(creds Creds) (*Salesforce, error) {
var auth *authentication
var err error
if creds != (Creds{}) &&
creds.Domain != "" && creds.Username != "" &&
creds.Password != "" && creds.SecurityToken != "" &&
creds.ConsumerKey != "" && creds.ConsumerSecret != "" {
if creds != (Creds{}) && creds.Domain != "" && creds.ConsumerKey != "" && creds.ConsumerSecret != "" &&
(creds.Username == "" || creds.Password == "" || creds.SecurityToken == "") {

auth, err = loginPassword(
auth, err = clientCredentialsFlow(
creds.Domain,
creds.ConsumerKey,
creds.ConsumerSecret,
)
} else if creds != (Creds{}) && creds.Domain != "" && creds.ConsumerKey != "" && creds.ConsumerSecret != "" &&
creds.Username != "" && creds.Password != "" && creds.SecurityToken != "" {

auth, err = usernamePasswordFlow(
creds.Domain,
creds.Username,
creds.Password,
Expand All @@ -204,8 +210,10 @@ func Init(creds Creds) (*Salesforce, error) {
)
}

if err != nil || auth == nil {
return nil, errors.New("please refer to salesforce REST API developer guide for proper authentication: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_flows.htm&type=5")
if err != nil {
return nil, err
} else if auth == nil || auth.AccessToken == "" {
return nil, errors.New("unknown authentication error")
}
return &Salesforce{auth: auth}, nil
}
Expand Down

0 comments on commit 5d94e55

Please sign in to comment.