Commit ebcc1d4
Changed files (5)
proxy/proxy.go → proxyauth/proxyauth.go
@@ -1,4 +1,4 @@
-package proxy
+package proxyauth
import (
"crypto/sha256"
@@ -8,6 +8,7 @@ import (
"net/http"
"os"
+ log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
)
@@ -56,6 +57,10 @@ func NewProxy(filePath string) (*Proxy, error) {
for i, d := range p.Domains {
for j, u := range d.Users {
p.Domains[i].Users[j].Password = "{SHA256}" + b64sha256(u.Password)
+ log.WithFields(log.Fields{
+ "domain": d.Address,
+ "user": u.Username,
+ }).Info("User Initalized")
}
}
return p, nil
@@ -90,6 +95,7 @@ func (d *Domain) get(reqUser string) (*User, error) {
// Assumption: 200 OK is golang default
func writeSuccess(w http.ResponseWriter, success bool) {
var r *Response
+ w.Header().Set("Content-Type", "application/json")
out := json.NewEncoder(w)
if success {
r = &Response{
@@ -101,6 +107,7 @@ func writeSuccess(w http.ResponseWriter, success bool) {
Reason: "denied by policy",
}
}
+
out.Encode(r)
}
@@ -127,50 +134,66 @@ func successBody(success bool) string {
// Response and Status Code
func (p *Proxy) Authenticate() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ logfields := log.Fields{
+ "Method": r.Method,
+ }
// domain lookup
vars := mux.Vars(r)
urlDomain, ok := vars["domain"]
if !ok {
w.WriteHeader(500) // Server error
+ logfields["Status"] = 500
+ log.WithFields(logfields).Warn("Domain parsing failed")
return
}
d, err := p.get(urlDomain)
+ logfields["Domain"] = urlDomain
if err != nil {
w.WriteHeader(404) // No such domain
+ logfields["Status"] = 404
+ log.WithFields(logfields).Info("No such domain")
return
}
// parse parameters
err = r.ParseForm()
if err != nil {
- w.WriteHeader(500) // Server error
+ w.WriteHeader(500)
+ logfields["Status"] = 500
+ log.WithFields(logfields).Warn("Parse form failure")
return
}
username := r.Form.Get("username")
if username == "" {
- writeSuccess(w, false) // no username provided
+ writeSuccess(w, false)
+ log.WithFields(logfields).Info("No username provided")
return
}
+ logfields["Username"] = username
password := r.Form.Get("password")
if password == "" {
- writeSuccess(w, false) // no password
+ writeSuccess(w, false)
+ log.WithFields(logfields).Info("No password provided")
return
}
// user lookup
u, err := d.get(username)
if err != nil {
- writeSuccess(w, false) // no such user
+ writeSuccess(w, false)
+ log.WithFields(logfields).Info("No such user")
return
}
// password validation
if u.Password != password {
- writeSuccess(w, false) // password mismatch
+ writeSuccess(w, false)
+ log.WithFields(logfields).Info("Password mismatch")
return
} else {
- writeSuccess(w, true) // successful authenticaton
+ writeSuccess(w, true)
+ log.WithFields(logfields).Info("Successful authentication")
return
}
proxyauth/proxyauth_test.go
@@ -0,0 +1,92 @@
+package proxyauth
+
+import (
+ "bytes"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "testing"
+
+ "github.com/gorilla/mux"
+)
+
+// We need access to the top level gorilla/mux router which, being a mux, also
+// implements the ServeHTTP(w,r) interface which we will call in the tests
+func routerInit() *mux.Router {
+ p, err := NewProxy("../users.json")
+ if err != nil {
+ log.Fatalf("Proxy init failed: %s", err)
+ }
+
+ r := mux.NewRouter()
+ // define the api method expectations, useful for future api handles
+ api := r.
+ Methods("POST").
+ Headers("Content-Type", "application/x-www-form-urlencoded").
+ Subrouter()
+
+ api.Handle("/api/2/domains/{domain}/proxyauth", p.Authenticate())
+ return r
+}
+
+func TestAuthenticate(t *testing.T) {
+
+ var tests = []struct {
+ domain string
+ username string
+ password string
+ code int
+ success bool
+ }{
+ // Case1 Success, topcoder.com domain, StatusCode 200
+ {"topcoder.com", "takumi", "{SHA256}2QJwb00iyNaZbsEbjYHUTTLyvRwkJZTt8yrj4qHWBTU=", 200, true},
+
+ // Case2 Success, appirio.com domain, StatusCode 200
+ {"appirio.com", "jun", "{SHA256}/Hnfw7FSM40NiUQ8cY2OFKV8ZnXWAvF3U7/lMKDwmso=", 200, true},
+
+ // Case3 Failure, password unmatch, StatusCode 200
+ {"topcoder.com", "takumi", "{SHA256}/Hnfw7FSM40NiUQ8cY2OFKV8ZnXWAvF3U7/lMKDwmso=", 200, false},
+
+ // Case4 Failure, username not found, StatusCode 200
+ {"topcoder.com", "bryfry", "{SHA256}2QJwb00iyNaZbsEbjYHUTTLyvRwkJZTt8yrj4qHWBTU=", 200, false},
+
+ // Case5 Failure, domain not found, StatusCode 404
+ {"bryfry.com", "takumi", "{SHA256}2QJwb00iyNaZbsEbjYHUTTLyvRwkJZTt8yrj4qHWBTU=", 404, false},
+ }
+
+ r := routerInit()
+
+ for _, test := range tests {
+
+ // setup request and parameters
+ data := url.Values{}
+ if test.username != "" {
+ data.Set("username", test.username)
+ }
+ if test.password != "" {
+ data.Set("password", test.password)
+ }
+ url := "/api/2/domains/" + test.domain + "/proxyauth"
+ req, _ := http.NewRequest("POST", url, bytes.NewBufferString(data.Encode()))
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+
+ // record request response
+ rw := httptest.NewRecorder()
+ rw.Body = new(bytes.Buffer)
+
+ // make request
+ r.ServeHTTP(rw, req)
+
+ // ensure got (g) equals want (w)
+ if g, w := rw.Code, test.code; g != w {
+ t.Errorf("%s: code = %d, want %d", url, g, w)
+ }
+ if rw.Code == 200 {
+ if g, w := rw.Body.String(), successBody(test.success); g != w {
+ t.Errorf("%s: body = %q, want %q", url, g, w)
+ }
+ }
+ }
+
+}
main.go
@@ -1,34 +1,107 @@
package main
import (
- "log"
+ "fmt"
"net/http"
+ "os"
+ "time"
- "./proxy"
+ "tc_swas/proxyauth"
+
+ log "github.com/Sirupsen/logrus"
+ "github.com/codegangsta/cli"
"github.com/gorilla/mux"
)
// TODO make cli interface with useful help and file handle
-// TODO ensure/test response is application/json
// TODO generate godoc
// TODO write a nice README.md
-// TODO logging
-func main() {
+func cliInit() *cli.App {
+ app := cli.NewApp()
+ app.Name = "ProxyAuth Server"
+ app.Usage = "Authentication API endpoint"
+ app.Version = "0.0.1"
+ app.Flags = []cli.Flag{
+ cli.StringFlag{
+ Name: "users, u",
+ Value: "./users.json",
+ Usage: "Specify users json file",
+ },
+ cli.IntFlag{
+ Name: "port, p",
+ Value: 80,
+ Usage: "Specify API Port",
+ },
+ cli.BoolFlag{
+ Name: "verbose",
+ Usage: "Increase verbosity",
+ },
+ }
+ return app
+}
- p, err := proxy.NewProxy("./users.json")
+func proxyInit(file string) *proxyauth.Proxy {
+ p, err := proxyauth.NewProxy(file)
if err != nil {
- log.Fatalf("Proxy init failed: %s", err)
+ log.WithFields(log.Fields{
+ "file": file,
+ "err": err,
+ }).Fatal("Proxy init failed")
}
+ return p
+}
+// Define the api handles, expected methods, and content type
+func apiInit(p *proxyauth.Proxy) *mux.Router {
r := mux.NewRouter()
- // define the api method expectations, useful for future api handles
api := r.
Methods("POST").
Headers("Content-Type", "application/x-www-form-urlencoded").
Subrouter()
- api.Handle("/api/2/domains/{domain}/proxyauth/", p.Authenticate())
- http.ListenAndServe(":8080", r) // TODO make cli argument
+ api.Handle("/api/2/domains/{domain}/proxyauth", p.Authenticate())
+ return r
+}
+
+// The httpInterceptor pattern is used to intercept all http requests. This
+// enables logging on all requests before they reach the mux router
+func httpInterceptor(router http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ startTime := time.Now()
+ router.ServeHTTP(w, r)
+ elapsedTime := time.Now().Sub(startTime)
+
+ log.WithFields(log.Fields{
+ "time": elapsedTime,
+ }).Info(r.URL.Path)
+
+ })
+}
+
+func main() {
+
+ app := cliInit()
+
+ app.Action = func(c *cli.Context) {
+
+ log.WithFields(log.Fields{"users": c.String("users")}).Info("Proxy Auth Initalizing Users...")
+ proxy := proxyInit(c.String("users"))
+
+ log.WithFields(log.Fields{"port": c.Int("port")}).Info("Proxy Auth API Server Starting...")
+ router := apiInit(proxy)
+ address := fmt.Sprintf(": %d", c.Int("port"))
+
+ if c.Bool("verbose") {
+ log.SetLevel(log.DebugLevel)
+ }
+
+ // using default router with interceptor pattern (uses api mux)
+ http.Handle("/", httpInterceptor(router))
+ log.Fatal(http.ListenAndServe(address, nil))
+ }
+
+ app.Run(os.Args)
}
README.md
@@ -1,138 +1,17 @@
-tc-swas
-=======
+#tc-swas
-##Challenge Overview
-
-Below are a copy of the challange details from TopCoder's Go Learning Challenge - Simple Web-API Server.
-
-See the [contest page](http://www.topcoder.com/challenge-details/30046011/?type=develop) for more info.
-
-Welcome to the simple web API server challenge !
-
-This is the first challenge of our second stage ‘Develop Backend Services with Golang’. The Goal of this challenge is to develop a simple web API server with Golang. The server provides an API to authenticate users.
-
-This time we will evaluate your submission based on the scorecard. We totally recommend you to read the articles mentioned in ‘Final Submission Guidelines - Code Guidelines’ section. The person who gets the highest score wins. In case of a tie, the person to submit earlier wins.
-
-If you have any questions, ask and get clarification in the forum.
-
-##API Spec
-This API is to authenticate user for a domain by username and password via HTTP. The domain name is included as a part of the endpoint.
-
-###Endpoint
-`/api/2/domains/{domain name}/proxyauth`
-
- We use port 80 but we would like to use other ports such as 8080 for testing.
-
-###Request
-####Request Method
-`POST`
-
-####Parameters
-* `username`
-* `password`
-
-‘password’ parameter is encrypted with the following logic
-
-`‘{SHA256}’ + Base64 encoded SHA256 digest of the user’s password`
-
-Example
+## Install Dependencies
```
-original password : abcd1234
-password parameter : {SHA256}6c7nGrky_ehjM40Ivk3p3-OeoEm9r7NCzmWexUULaa4=
+go get github.com/codegangsta/cli # MIT License
+go get github.com/gorilla/mux # New BSD Licensed
```
-####ContentType
-`application/x-www-form-urlencoded`
-
-####Sample
-Request parameters
-```
-domain name : topcoder.com
-username : takumi
-password : {SHA256}2QJwb00iyNaZbsEbjYHUTTLyvRwkJZTt8yrj4qHWBTU=
-Original password is ‘ilovego’
-```
-Request to a server running on localhost with cURL
-`curl --data "username=takumi&password={SHA256}2QJwb00iyNaZbsEbjYHUTTLyvRwkJZTt8yrj4qHWBTU=" http://localhost/api/2/domains/topcoder.com/proxyauth`
-
-###Response
-####StatusCode
-Use 200 to indicate that the request is processed successfully. Even if we get some application errors such as ‘password unmatch’ or validation errors of parameters, status code should be 200. 404 is used when the domain name is not supported. 500 is used for system errors.
-* 200 Successfully processed the request
-* 404 No such domain
-* 500 Server error
-
-####Format
-Return JSON data for status code 200.
-In case of success.
-```
-{
- "access_granted": true
-}
-```
-In case of authentication failure or validation errors. The 'reason' is always same.
-```
-{
- "access_granted": false, "reason": "denied by policy"
-}
-```
-No data should be returned for status code 404 and 500.
-
-####ContentType
-`application/json`
-
-###Authentication Logic
-This time we use a json file attached (users.json) for data store.
-
-When you receive a request to appirio.com domain with username ‘jun’ and password, you are supposed to find a record for jun under appirio.com domain in users.json. Encrypt jun’s password you get from the json file, then compare the encrypted password and the password received. If they are same, the authentication succeeds.
-
-###Note
-No need to handle signals for this challenge
-
-###Test
-Prepare your test script to cover the following cases.
-```
-Case1 Success
-topcoder.com domain
-StatusCode 200
-
-Case2 Success
-appirio.com domain
-StatusCode 200
-
-Case3 Failure
-password unmatch
-StatusCode 200
-
-Case4 Failure
-username not found
-StatusCode 200
-
-Case5 Failure
-domain not found
-StatusCode 404
-```
-
-##Final Submission Guidelines
-
-###Code Guidelines
-Follow the practices mentioned in the articles below.
- * http://golang.org/doc/effective_go.html
- * https://code.google.com/p/go-wiki/wiki/CodeReviewComments#Go_Code_Review_Comments
-We have one note specific to this challenge.
-
-Use ‘lower_case_with_underscore’ name for package, file or directory. However, try to avoid underscores and prefer short names
+## Build
+`go build`
-##Submission Deliverables
+## Run
+`./proxyauth_server`
-###Source code
-* Format your code with ‘gofmt’ command.
-* Test script that covers the test cases
-* Simple README to explain your deliverables
+## Help
+`./proxyauth_server --help`
-###External Libraries
-We believe this challenge is not so complicated that we can complete without any external libraries. But if you would like to use external libraries please follow the guidelines below.
-* Do not use libraries developed with languages other than Golang
-* Do not use GPL libraries and LGPL libraries
-* MIT, Apache and BSD libraries are available
-* Please mention about external libraries you used in your README
SPEC.md
@@ -0,0 +1,135 @@
+#Challenge Overview
+
+Below are a copy of the challange details from TopCoder's Go Learning Challenge - Simple Web-API Server.
+
+See the [contest page](http://www.topcoder.com/challenge-details/30046011/?type=develop) for more info.
+
+Welcome to the simple web API server challenge !
+
+This is the first challenge of our second stage ‘Develop Backend Services with Golang’. The Goal of this challenge is to develop a simple web API server with Golang. The server provides an API to authenticate users.
+
+This time we will evaluate your submission based on the scorecard. We totally recommend you to read the articles mentioned in ‘Final Submission Guidelines - Code Guidelines’ section. The person who gets the highest score wins. In case of a tie, the person to submit earlier wins.
+
+If you have any questions, ask and get clarification in the forum.
+
+#API Spec
+This API is to authenticate user for a domain by username and password via HTTP. The domain name is included as a part of the endpoint.
+
+##Endpoint
+`/api/2/domains/{domain name}/proxyauth`
+
+ We use port 80 but we would like to use other ports such as 8080 for testing.
+
+##Request
+###Request Method
+`POST`
+
+###Parameters
+* `username`
+* `password`
+
+‘password’ parameter is encrypted with the following logic
+
+`‘{SHA256}’ + Base64 encoded SHA256 digest of the user’s password`
+
+Example
+```
+original password : abcd1234
+password parameter : {SHA256}6c7nGrky_ehjM40Ivk3p3-OeoEm9r7NCzmWexUULaa4=
+```
+
+###ContentType
+`application/x-www-form-urlencoded`
+
+###Sample
+Request parameters
+```
+domain name : topcoder.com
+username : takumi
+password : {SHA256}2QJwb00iyNaZbsEbjYHUTTLyvRwkJZTt8yrj4qHWBTU=
+Original password is ‘ilovego’
+```
+Request to a server running on localhost with cURL
+`curl --data "username=takumi&password={SHA256}2QJwb00iyNaZbsEbjYHUTTLyvRwkJZTt8yrj4qHWBTU=" http://localhost/api/2/domains/topcoder.com/proxyauth`
+
+##Response
+###StatusCode
+Use 200 to indicate that the request is processed successfully. Even if we get some application errors such as ‘password unmatch’ or validation errors of parameters, status code should be 200. 404 is used when the domain name is not supported. 500 is used for system errors.
+* 200 Successfully processed the request
+* 404 No such domain
+* 500 Server error
+
+###Format
+Return JSON data for status code 200.
+In case of success.
+```
+{
+ "access_granted": true
+}
+```
+In case of authentication failure or validation errors. The 'reason' is always same.
+```
+{
+ "access_granted": false, "reason": "denied by policy"
+}
+```
+No data should be returned for status code 404 and 500.
+
+###ContentType
+`application/json`
+
+##Authentication Logic
+This time we use a json file attached (users.json) for data store.
+
+When you receive a request to appirio.com domain with username ‘jun’ and password, you are supposed to find a record for jun under appirio.com domain in users.json. Encrypt jun’s password you get from the json file, then compare the encrypted password and the password received. If they are same, the authentication succeeds.
+
+##Note
+No need to handle signals for this challenge
+
+##Test
+Prepare your test script to cover the following cases.
+```
+Case1 Success
+topcoder.com domain
+StatusCode 200
+
+Case2 Success
+appirio.com domain
+StatusCode 200
+
+Case3 Failure
+password unmatch
+StatusCode 200
+
+Case4 Failure
+username not found
+StatusCode 200
+
+Case5 Failure
+domain not found
+StatusCode 404
+```
+
+#Final Submission Guidelines
+
+###Code Guidelines
+Follow the practices mentioned in the articles below.
+ * http://golang.org/doc/effective_go.html
+ * https://code.google.com/p/go-wiki/wiki/CodeReviewComments#Go_Code_Review_Comments
+We have one note specific to this challenge.
+
+Use ‘lower_case_with_underscore’ name for package, file or directory. However, try to avoid underscores and prefer short names
+
+#Submission Deliverables
+
+##Source code
+* Format your code with ‘gofmt’ command.
+* Test script that covers the test cases
+* Simple README to explain your deliverables
+
+##External Libraries
+We believe this challenge is not so complicated that we can complete without any external libraries. But if you would like to use external libraries please follow the guidelines below.
+* Do not use libraries developed with languages other than Golang
+* Do not use GPL libraries and LGPL libraries
+* MIT, Apache and BSD libraries are available
+* Please mention about external libraries you used in your README