242 lines
6.4 KiB
Go
242 lines
6.4 KiB
Go
package freeipa
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Unused port for testing.
|
|
const httpsPort = 8831
|
|
|
|
// Test login handler.
|
|
func handleLogin(w http.ResponseWriter, req *http.Request) {
|
|
// Logins are form data posts.
|
|
req.ParseForm()
|
|
|
|
// Check username/password equals test credentials.
|
|
user := req.Form.Get("user")
|
|
password := req.Form.Get("password")
|
|
if user == "test" && password == "testpassword" {
|
|
// Successful login send session cookie.
|
|
cookie := http.Cookie{}
|
|
cookie.Name = "ipa_session"
|
|
cookie.Value = "correct-session-secret"
|
|
cookie.Expires = time.Now().Add(30 * time.Minute)
|
|
cookie.Secure = true
|
|
cookie.HttpOnly = true
|
|
cookie.Path = "/ipa"
|
|
http.SetCookie(w, &cookie)
|
|
w.Header().Set("IPASESSION", "correct-session-secret")
|
|
} else {
|
|
// Invalid login, send rejection.
|
|
w.Header().Set("X-IPA-Rejection-Reason", "invalid-password")
|
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
fmt.Fprintf(w, `<html>
|
|
<head>
|
|
<title>401 Unauthorized</title>
|
|
</head>
|
|
<body>
|
|
<h1>Invalid Authentication</h1>
|
|
<p>
|
|
<strong>kinit: Password incorrect while getting initial credentials
|
|
</strong>
|
|
</p>
|
|
</body>
|
|
</html>`)
|
|
}
|
|
}
|
|
|
|
// Send JSON file to HTTP request.
|
|
func sendJSONFile(w http.ResponseWriter, filePath string) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer f.Close()
|
|
io.Copy(w, f)
|
|
}
|
|
|
|
// General invalid json error response for testing error handling.
|
|
func sendInvalidJSON(w http.ResponseWriter) {
|
|
sendJSONFile(w, "test/invalid_json.json")
|
|
}
|
|
|
|
// Handle the json session test request.
|
|
func handleJSON(w http.ResponseWriter, req *http.Request) {
|
|
// If session cookie doesn't exist, something is wrong. Send unauthenticated response.
|
|
cookie, err := req.Cookie("ipa_session")
|
|
if err != nil || cookie.Value != "correct-session-secret" {
|
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Generally json response from here.
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
// Get the request body and parse it out.
|
|
res := new(Request)
|
|
err = json.NewDecoder(req.Body).Decode(res)
|
|
if err != nil {
|
|
// If the json decode fails, send the error.
|
|
sendInvalidJSON(w)
|
|
return
|
|
}
|
|
|
|
// For testing, we'll consider user_add/user_find as an accepted method, all others will error.
|
|
if res.Method == "user_add" {
|
|
// Send user add response data.
|
|
sendJSONFile(w, "test/user_add_response.json")
|
|
} else if res.Method == "user_find" {
|
|
// Send user add response data.
|
|
sendJSONFile(w, "test/user_find_response.json")
|
|
} else {
|
|
// An unexpected method received for testing, send error message.
|
|
sendInvalidJSON(w)
|
|
}
|
|
}
|
|
|
|
// General library tests with test server.
|
|
func TestClient(t *testing.T) {
|
|
// Spin up test server using port specified above.
|
|
srvAddr := fmt.Sprintf("127.0.0.1:%d", httpsPort)
|
|
http.HandleFunc("/ipa/session/login_password", handleLogin)
|
|
http.HandleFunc("/ipa/session/json", handleJSON)
|
|
isListening := make(chan bool)
|
|
go func() {
|
|
l, err := net.Listen("tcp", srvAddr)
|
|
if err != nil {
|
|
log.Fatal("Listen: ", err)
|
|
}
|
|
isListening <- true
|
|
err = http.ServeTLS(l, nil, "test/cert.pem", "test/key.pem")
|
|
if err != nil {
|
|
log.Fatal("Serve: ", err)
|
|
}
|
|
}()
|
|
// Allow the http server to initialize.
|
|
<-isListening
|
|
|
|
// Test server has a self signed certificate, ignore invalid certs.
|
|
transportConfig := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
|
|
// Connect using wrong password to confirm invalid login responses are handled correctly.
|
|
_, err := Connect(srvAddr, transportConfig, "test", "wrong-password")
|
|
if err == nil || err.Error() != "login failed: unauthorized response <invalid-password> (1201)" {
|
|
t.Fatalf("expected login failure")
|
|
}
|
|
|
|
// Connect using correct password to confirm valid logins are handled correctly.
|
|
client, err := Connect(srvAddr, transportConfig, "test", "testpassword")
|
|
if err != nil {
|
|
t.Fatalf("error: %s", err)
|
|
}
|
|
|
|
// Setup test user_add request.
|
|
params := make(map[string]interface{})
|
|
params["givenname"] = "FreeIPA"
|
|
params["sn"] = "Test"
|
|
params["userpassword"] = "test-password"
|
|
req := NewRequest(
|
|
"user_add",
|
|
[]interface{}{"username"},
|
|
params,
|
|
)
|
|
|
|
// Send the request to the test server.
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("error: %s", err)
|
|
}
|
|
|
|
// Test reading bool key from response.
|
|
v, _ := resp.GetBool("has_keytab")
|
|
if !v {
|
|
t.Errorf("expected true boolean")
|
|
}
|
|
|
|
// Test reading string from response.
|
|
s, _ := resp.GetString("krbcanonicalname")
|
|
if s != "username@EXAMPLE.COM" {
|
|
t.Errorf("unexpected string: %s", s)
|
|
}
|
|
|
|
// Test reading date from response.
|
|
d, _ := resp.GetDateTime("krblastpwdchange")
|
|
year, month, day := d.Date()
|
|
if year != 2023 || month != 8 || day != 10 {
|
|
t.Errorf("unexpected date: %s", d)
|
|
}
|
|
|
|
// Test reading base64 data from response.
|
|
b, _ := resp.GetData("krbextradata")
|
|
if len(b) != 27 {
|
|
t.Errorf("unexpected data: %v", b)
|
|
}
|
|
|
|
// Test reading a non-existant value from response.
|
|
s, ok := resp.GetString("non-existant")
|
|
if s != "" || ok {
|
|
t.Errorf("expected empty string: %s", s)
|
|
}
|
|
|
|
a, ok := resp.GetStrings("objectclass")
|
|
if !ok || len(a) != 13 {
|
|
t.Errorf("unexpected data: %v", a)
|
|
}
|
|
|
|
// Test receiving an error message from the test server.
|
|
req.Method = "invalid"
|
|
_, err = client.Do(req)
|
|
if err == nil || err.Error() != "JSONError (909): Invalid JSON-RPC request: Expecting property name enclosed in double quotes: line 4 column 4 (char 44)" {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// Test user_find.
|
|
params = make(map[string]interface{})
|
|
params["pkey_only"] = true
|
|
params["sizelimit"] = 0
|
|
req = NewRequest(
|
|
"user_find",
|
|
[]interface{}{""},
|
|
params,
|
|
)
|
|
|
|
// Send the request to the test server.
|
|
resp, err = client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("error: %s", err)
|
|
}
|
|
|
|
// The response should have a count as its an array.
|
|
if resp.Result.Count != 2 {
|
|
t.Error("expected 2 users")
|
|
}
|
|
|
|
// Confirm the array actually counts the same.
|
|
if resp.CountResults() != 2 {
|
|
t.Error("expected 2 users")
|
|
}
|
|
|
|
// Confirm an string at index works withou the array encapsulation.
|
|
dn, _ := resp.GetStringAtIndex(0, "dn")
|
|
if dn != "uid=admin,cn=users,cn=accounts,dc=example,dc=com" {
|
|
t.Errorf("unexpected string: %s", dn)
|
|
}
|
|
|
|
// Confirm the UID string at index works because it is array encapsulated.
|
|
uid, _ := resp.GetStringAtIndex(1, "uid")
|
|
if uid != "johnny.bravo" {
|
|
t.Errorf("unexpected string: %s", uid)
|
|
}
|
|
}
|