[
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: go\n\ninstall:\n - go get -u github.com/jteeuwen/go-bindata/...\n - go get -u github.com/dgrijalva/jwt-go/...\n - go get -u github.com/gorilla/mux/...\n - go get -u gopkg.in/ldap.v2/...\n - go get -u github.com/gorilla/handlers/...\n - go get -u github.com/samitpal/goProbe/...\n\nbefore_script:\n - go generate\ngo:\n - 1.5\n\nscript:\n - go vet ./...\n - go test ./...\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) [2016] [Samit Pal]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://travis-ci.org/samitpal/simple-sso.svg?branch=master)](https://travis-ci.org/samitpal/simple-sso)\n\n[google group](https://groups.google.com/forum/#!forum/simple-sso)\n\nSummary\n------------------\nsimple-sso is an SSO service with support for roles based authorization written in the Go programming language. \n\nFor browser based applications the service exposes the /sso handler which sets the sso cookie for a given domain. For instance if the login service runs as login.example.com, the sso cookie domain could be configured as example.com. That way any application running under a subdomain of example.com will be able to leverage the sso service (see [rfc6265](https://tools.ietf.org/html/rfc6265#page-6)). The value of the sso cookie is a [jwt](https://jwt.io/) token signed by the rsa private key of the simple-sso service. To use this service the application needs to have the corresponding public key in order to decrypt the cookie. The app checks for the presence of the sso cookie and in the absence of that it redirects to the /sso handler of the sample-sso service setting the **s_url** parameter to its url. The login service is expected to redirect the user back to **s_url** post authentication. See the code under example_app directory.\n\nsimple-sso exposes /auth_token handler which can be used to download the encrypted jwt token. The downloaded token can potentially be passed via Authorization headers by client applications to server apps hopefully using ssl.\n\nsimple-sso also has a form of authorization capabilities. It can optionally pack in the roles (e.g openldap groups) information in the cookie/jwt based on a config environment variables..\n\nThey say a picture is thousand times more effective, so here is a diagram which shows traffic flow with simple-sso.\n\n![alt tag](https://docs.google.com/drawings/d/1blQbqjT4lb0nu_lX-WO2OaQPvhg5I2pF0LvPZnQ9ywA/pub?w=960&h=720)\n\nInstallation\n-------------------\n##### To build from source follow the steps below: \n\n```sh\n$ go get -u github.com/jteeuwen/go-bindata/...\n\n$ go get -u github.com/samitpal/simple-sso/...\n\n$ export PATH=$PATH:$GOPATH/bin\n\n$ go generate\n\n$ go install\n```\n\nRunning the binary\n-------------------\n\nJust run the simple-sso binary. Following principles of 12 factor app, simple-sso uses environment variables for its configurations. These are.\n\n| Variable      | Default value | Purpose |\n|---------------|--------------|------------|\n| sso_ssl_cert_path  |  ssl_certs/cert.pem | ssl certificate path. |\n| sso_ssl_key_path  |ssl_certs/key.pem   | ssl certificate private key. |\n| sso_private_key_path  | key_pair/demo.rsa  | rsa private key path used to sign the token. |\n| sso_weblog_dir  |  - | Directory path where access hits are logged. |\n| sso_user_roles  | false  | Whether to pack in the roles info within the token. |\n| sso_cookie_name  | SSO_C  | Name of the sso cookie. |\n| sso_cookie_domain  | 127.0.0.1  | Domain name of the cookie. |\n| sso_cookie_validhours  | 20  | Cookie validity in hours. |\n| sso_ldap_host  | localhost  | Ldap host. |\n| sso_ldap_port  | 389  | Ldap host port. |\n| sso_ldap_ssl  | false  | whether to use ssl. |\n| sso_ldap_basedn  | - | Ldap base dn. |\n| sso_ldap_binddn  | - | Ldap bind dn if anonymous bind is disallowed. |\n| sso_ldap_bindpasswd  | - | Ldap bind password if anonymous bind is disallowed. |\n\n\nCaveats\n------------------\n* Since time is of essence in this infrastructure, the server time needs to be set and managed correctly.\n* Communication between this service and the ldap infrastruture should be encrypted.\n* This has been tested with openldap."
  },
  {
    "path": "example_app/main.go",
    "content": "package main\n\nimport (\n\t\"crypto/rsa\"\n\t\"fmt\"\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gorilla/mux\"\n\t\"github.com/samitpal/simple-sso/util\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n)\n\nvar parsedPubKey *rsa.PublicKey\n\nfunc init() {\n\tkey, _ := ioutil.ReadFile(\"../key_pair/demo.rsa.pub\") // this is the public key of the login (simple-sso) server\n\tparsedPubKey, _ = jwt.ParseRSAPublicKeyFromPEM(key)\n}\n\nfunc cookieCheck(w http.ResponseWriter, r *http.Request) {\n\tc, err := r.Cookie(\"SSO_C\")\n\tif err == http.ErrNoCookie {\n\t\t// we redirect to the login service setting the appropriate s_url to come back after auth.\n\t\thttp.Redirect(w, r, \"https://127.0.0.1:8081/sso?s_url=https://127.0.0.1:8082/cookie\", 301)\n\t\treturn\n\t}\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tparts := strings.Split(strings.Split(c.String(), \"=\")[1], \".\")\n\terr = jwt.SigningMethodRS512.Verify(strings.Join(parts[0:2], \".\"), parts[2], parsedPubKey)\n\tif err != nil {\n\t\tlog.Fatalf(\"[%v] Error while verifying key: %v\", strings.Split(c.String(), \"=\")[1], err)\n\t}\n\n\ttokenString := strings.Split(c.String(), \"=\")[1]\n\ttoken, err := jwt.ParseWithClaims(tokenString, &util.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {\n\t\treturn parsedPubKey, nil\n\t})\n\n\tclaims, ok := token.Claims.(*util.CustomClaims) // claims.User and claims.Roles are what we are interested in.\n\tif ok && token.Valid {\n\t\tfmt.Printf(\"User: %v Roles: %v Tok_Expires: %v \\n\", claims.User, claims.Roles, claims.StandardClaims.ExpiresAt)\n\n\t} else {\n\t\tfmt.Println(err)\n\t}\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintf(w, \"You have visited a cookietest page.\\n\\n\")\n\tfmt.Fprintf(w, \"User: %v, Roles: %v, Tok_Expires: %v\\n\", claims.User, claims.Roles, claims.StandardClaims.ExpiresAt)\n\treturn\n}\n\nfunc authTokCheck(w http.ResponseWriter, r *http.Request) {\n\th := r.Header.Get(\"Authorization\")\n\tif h == \"\" {\n\t\thttp.Error(w, \"No Authorization header\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tparts := strings.Split(strings.Split(h, \" \")[1], \".\")\n\terr := jwt.SigningMethodRS512.Verify(strings.Join(parts[0:2], \".\"), parts[2], parsedPubKey)\n\tif err != nil {\n\t\tlog.Fatalf(\"[%v] Error while verifying key: %v\", strings.Split(h, \"=\")[1], err)\n\t}\n\n\ttokenString := strings.Split(h, \" \")[1]\n\ttoken, err := jwt.ParseWithClaims(tokenString, &util.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {\n\t\treturn parsedPubKey, nil\n\t})\n\n\tclaims, ok := token.Claims.(*util.CustomClaims) // claims.User and claims.Roles are what we are interested in.\n\tif ok && token.Valid {\n\t\tfmt.Printf(\"User: %v Roles: %v Tok_Expires: %v \\n\", claims.User, claims.Roles, claims.StandardClaims.ExpiresAt)\n\n\t} else {\n\t\tfmt.Println(err)\n\t}\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintf(w, \"You have visited a auth tokentest page.\\n\\n\")\n\tfmt.Fprintf(w, \"User: %v, Roles: %v, Tok_Expires: %v\\n\", claims.User, claims.Roles, claims.StandardClaims.ExpiresAt)\n\treturn\n}\nfunc main() {\n\tlog.Println(\"Starting app server.\")\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/cookie\", cookieCheck)\n\tr.HandleFunc(\"/auth_token\", authTokCheck)\n\n\thttp.Handle(\"/\", r)\n\terr := http.ListenAndServeTLS(\":8082\", \"../ssl_certs/cert.pem\", \"../ssl_certs/key.pem\", nil)\n\tif err != nil {\n\t\tlog.Fatal(\"ListenAndServe: \", err)\n\t}\n}\n"
  },
  {
    "path": "key_pair/README.md",
    "content": "This directory contains the rsa key pair. simple-sso uses the private key to sign the jwt tokens. The client apps need to use the public key\nto decrypt the same"
  },
  {
    "path": "key_pair/demo.rsa",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA1YOCGREepbXPOou0dLcZy8XXp4jD1If6fVFAYx/sa72/ih+B\nNGkxCuq20sLqoPQb273bixUPwWtjqWJCaHzf5WgPwh2zej7V3FiwnMTDuAPLdo+6\n0FWhEUSb9pZlnoxXIQWjUabixahmpyJVBdavbReBNQr3xkeio87vDSpSzI9qsfGl\nSkA2mm14GIgnBGaNEwxka+YTLZgZOMtNMw38zW+t2PVmy5ul+pFXMvG87FN4fiSu\ndQdIeS7YyWGUEuCV/091neOpp4oWdwAkik/+oVE4VcIUvgvsmtV6z3KNYUcYfRoC\nEWaBoe8I78hy+nssDUKlT7XBN56hIXek6P9TewIDAQABAoIBAFnAj0a0QJrOA0+L\n/I53jZtwDgg54IANrQlSx2sjt0FPIR4RwkFi2p/JLJMKJpEELFXByHD9qILY/qrs\nSBgeLgwEI2OpEpIXqdSXX552xAMtbTDomFINPMjCe4E7lXoBanrSIOYo7fjComwt\nbWon5dRI5iKC+sbZxA9x5GE3YljkR5lAu01RFbc0iDThNsBJYAFxsLgOmLgBF8xO\nfrmZt+CkgfNsFq1PguX3AoL7es6kwWtn2yLlIqxp/QOsuNuFnjo+hQ7WnaHyKqBF\nr/dbF58qXxvSnGWzVsz29hYglNWyTSPDUOwWeES14qGZsal9KUGSubd9MKNC3Dks\nT0aMdxECgYEA+x7m2Bt04RKLqtE5Oh5L9RhANZ7/B8qfgFIjKv7EJ2ovqTWUCVsy\nljHuSo2YN7kQ1/NfTGiOzv6JRdeNtwCqbBYU619KyhICVanUBz4i+6rtqQPhhp9U\nWuXRp1rjE3haxGBPkY+Ze58mMiMGLvOtjFA+5jze6AWiXB8ksz7olgMCgYEA2amL\nrwvxlPV8M+knBtRkIc7nCWZ6Lz2sL8VXHvqWQpvY6XVMXOo+OJxXGzw5LMo4th4q\nqEPxyuUpMuGt+3SYtD2w2+nL6bLNutmj1lSAW0Rac1tW7kH08RLTRaQhy/qbB5s/\npi1M24h0yPCuRmm+EhXeuOReP62seFc5lJw8bykCgYEA4Nye4MxVMGUm42JN2Bjg\n8ysv89PXkeaCRKlIDGvswU54NxBe6rHa7lrvgZqgvuTcjELFBuppVjjeOsf1gfT6\npaZwPQMrOR4/MO3Nil69fJVmEn4DKETriClaPn1H8FtJC6ciGLl5OhUcYrCyDMDu\nmkIQ0KGZCDJjXBIXDto58nkCgYEAmeX5L+GgBJS2JvYZdAjEa+shDFJ63eAbWQON\nIAhKKfqLmjYnsiKlr91K8aTZQEQTaSFXQ/YWhkEVqjZLj9nXBsn/vN5IIYsdT5oG\n78p7nwxrb9kLVBcqmzGWVE1C4DjnWK96h4LMLwUCnfkfIAYwMBVqjwxZX2jq44O4\n4My/JlECgYEAwIrodHWjWhY1h5R9N3NXCKP9QPuevJWgvkZjhXDo+nPZE7T37yMe\nI+Y1+yXGEKP0d56uhfnRg2GaIBOE+KMdC/mxRNYyhuIQRIro/N3brbILp1nK8Gpo\nPaBcKoBjqxclV7sDeHfzCCB4GQTAVa2HFPpYvLVRo8hSiR4bdSBcjVo=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "key_pair/demo.rsa.pub",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1YOCGREepbXPOou0dLcZ\ny8XXp4jD1If6fVFAYx/sa72/ih+BNGkxCuq20sLqoPQb273bixUPwWtjqWJCaHzf\n5WgPwh2zej7V3FiwnMTDuAPLdo+60FWhEUSb9pZlnoxXIQWjUabixahmpyJVBdav\nbReBNQr3xkeio87vDSpSzI9qsfGlSkA2mm14GIgnBGaNEwxka+YTLZgZOMtNMw38\nzW+t2PVmy5ul+pFXMvG87FN4fiSudQdIeS7YyWGUEuCV/091neOpp4oWdwAkik/+\noVE4VcIUvgvsmtV6z3KNYUcYfRoCEWaBoe8I78hy+nssDUKlT7XBN56hIXek6P9T\newIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "ldap/config.go",
    "content": "package ldap\n\nimport (\n\t\"crypto/rsa\"\n\t\"errors\"\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/samitpal/simple-sso/sso\"\n)\n\nvar PrivateKey *rsa.PrivateKey\nvar BaseConf *sso.BaseConfig\n\ntype LdapConfig struct {\n\thost       string\n\tport       int\n\tssl        bool\n\tbasedn     string\n\tbinddn     string\n\tbindPasswd string\n}\n\nfunc setupBaseConfig() {\n\tvar err error\n\tBaseConf, err = sso.SetupBaseConfig()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tprivateKeyData, err := ioutil.ReadFile(BaseConf.PrivateKeyPath)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyData)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\n// setDefaultString returns a given default string.\nfunc setDefaultString(s string, d string) string {\n\tif s == \"\" {\n\t\treturn d\n\t}\n\treturn s\n}\n\n// ldapConfig sets up ldap config from the env.\nfunc (l *LdapConfig) setupLdapConfig() error {\n\n\tl.host = setDefaultString(os.Getenv(sso.ConfMap[\"sso_ldap_host\"]), \"localhost\")\n\n\tport, err := strconv.Atoi(setDefaultString(os.Getenv(sso.ConfMap[\"sso_ldap_port\"]), \"389\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\tl.port = port\n\n\tssl, err := strconv.ParseBool(setDefaultString(os.Getenv(sso.ConfMap[\"sso_ldap_ssl\"]), \"false\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\tl.ssl = ssl\n\n\tl.basedn = os.Getenv(sso.ConfMap[\"sso_ldap_basedn\"])\n\tl.binddn = os.Getenv(sso.ConfMap[\"sso_ldap_binddn\"])\n\n\tl.bindPasswd = os.Getenv(sso.ConfMap[\"sso_ldap_bindpasswd\"])\n\n\tif l.binddn != \"\" && l.bindPasswd == \"\" {\n\t\treturn errors.New(\"Bind dn is set but bind password is not set.\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "ldap/config_test.go",
    "content": "package ldap\n\nimport (\n\t\"github.com/samitpal/simple-sso/sso\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestSetupDefaultString(t *testing.T) {\n\ts := \"return_me\"\n\td := \"not_me\"\n\tr := setDefaultString(s, d)\n\tif r != s {\n\t\tt.Errorf(\"Got: %s Want: %s\", r, s)\n\t}\n\n\ts1 := \"\"\n\td1 := \"return_me\"\n\tr = setDefaultString(s1, d1)\n\tif r != d1 {\n\t\tt.Errorf(\"Got: %s Want: %s\", r, d1)\n\t}\n}\n\nfunc TestSetupLdapConfig(t *testing.T) {\n\tos.Setenv(sso.ConfMap[\"sso_ldap_host\"], \"host\")\n\tos.Setenv(sso.ConfMap[\"sso_ldap_port\"], \"123\")\n\tos.Setenv(sso.ConfMap[\"sso_ldap_ssl\"], \"true\")\n\tos.Setenv(sso.ConfMap[\"sso_ldap_basedn\"], \"basedn\")\n\tos.Setenv(sso.ConfMap[\"sso_ldap_binddn\"], \"binddn\")\n\tos.Setenv(sso.ConfMap[\"sso_ldap_bindpasswd\"], \"bindpasswd\")\n\tl := LdapConfig{}\n\terr := l.setupLdapConfig()\n\tif err != nil {\n\t\tt.Errorf(\"Error: %v\", err)\n\t}\n\tw := LdapConfig{\"host\", 123, true, \"basedn\", \"binddn\", \"bindpasswd\"}\n\tif !reflect.DeepEqual(l, w) {\n\t\tt.Errorf(\"Got: %v\\n \\tWant: %v\", l, w)\n\t}\n\n\t_ = os.Unsetenv(sso.ConfMap[\"sso_ldap_host\"])\n\t_ = os.Unsetenv(sso.ConfMap[\"sso_ldap_port\"])\n\t_ = os.Unsetenv(sso.ConfMap[\"sso_ldap_ssl\"])\n\terr = l.setupLdapConfig()\n\tif err != nil {\n\t\tt.Errorf(\"Error: %v\", err)\n\t}\n\tw = LdapConfig{\"localhost\", 389, false, \"basedn\", \"binddn\", \"bindpasswd\"}\n\tif !reflect.DeepEqual(l, w) {\n\t\tt.Errorf(\"Got: %v\\n \\tWant: %v\", l, w)\n\t}\n\n}\n"
  },
  {
    "path": "ldap/ldap.go",
    "content": "// package ldap is an sso implementation. It uses an ldap backend to authenticate and optionally\n// utilize ldap group memberships for setting up roles in the cookie/jwt which can later be used\n// by applications for authorization.\npackage ldap\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"gopkg.in/ldap.v2\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/samitpal/simple-sso/sso\"\n\t\"github.com/samitpal/simple-sso/util\"\n)\n\ntype LdapSSO struct {\n\tCookie *sso.CookieConfig\n\tLdap   *LdapConfig\n}\n\nvar (\n\tErrUserNotFound = sso.ErrUserNotFound\n\tErrUnauthorized = sso.ErrUnAuthorized\n)\n\nfunc NewLdapSSO() (*LdapSSO, error) {\n\tsetupBaseConfig()\n\tc, err := sso.SetupCookieConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tl := new(LdapConfig)\n\terr = l.setupLdapConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &LdapSSO{c, l}, nil\n}\n\nfunc (ls LdapSSO) Auth(u string, p string) (*string, *[]string, error) {\n\n\tldap.DefaultTimeout = 20 * time.Second // applies to Dial and DialTLS methods.\n\tl, err := ldap.Dial(\"tcp\", fmt.Sprintf(\"%s:%d\", ls.Ldap.host, ls.Ldap.port))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tdefer l.Close()\n\n\t// Reconnect with TLS if sso_ldap_ssl env is set.\n\tif ls.Ldap.ssl {\n\t\terr = l.StartTLS(&tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\t// First bind with a read only user\n\tif ls.Ldap.binddn != \"\" {\n\t\terr = l.Bind(ls.Ldap.binddn, ls.Ldap.bindPasswd)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\t// Search for the given username\n\tsearchRequestUser := ldap.NewSearchRequest(\n\t\tls.Ldap.basedn,\n\t\tldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 30, false, // sets a time limit of 30 secs\n\t\tfmt.Sprintf(\"(&(objectClass=inetOrgPerson)(uid=%s))\", u),\n\t\t[]string{\"dn\"},\n\t\tnil,\n\t)\n\n\tsru, err := l.Search(searchRequestUser)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif len(sru.Entries) != 1 {\n\t\treturn nil, nil, ErrUserNotFound\n\t}\n\n\tuserdn := sru.Entries[0].DN\n\n\t// Bind as the user to verify their password\n\terr = l.Bind(userdn, p)\n\tif err != nil {\n\t\treturn nil, nil, ErrUnauthorized\n\t}\n\n\t// Now find the group membership (if sso_user_roles env is true).\n\tvar g []string\n\tif BaseConf.UserRoles {\n\t\tsearchRequestGroups := ldap.NewSearchRequest(\n\t\t\tls.Ldap.basedn,\n\t\t\tldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 30, false, // sets a time limit of 30 secs\n\t\t\tfmt.Sprintf(\"(&(objectClass=posixGroup)(memberUid=%s))\", u),\n\t\t\t[]string{\"cn\"},\n\t\t\tnil,\n\t\t)\n\t\tsrg, err := l.Search(searchRequestGroups)\n\t\tif err != nil {\n\t\t\treturn &u, nil, err\n\t\t}\n\n\t\tg = srg.Entries[0].GetAttributeValues(\"cn\")\n\t}\n\n\treturn &u, &g, nil\n}\n\nfunc (ls LdapSSO) CTValidHours() int64 {\n\treturn ls.Cookie.ValidHours\n}\n\nfunc (ls LdapSSO) BuildJWTToken(u string, g []string, exp time.Time) (string, error) {\n\treturn util.GenJWT(u, g, PrivateKey, exp.Unix())\n\n}\n\nfunc (ls LdapSSO) CookieName() string {\n\treturn ls.Cookie.Name\n}\n\nfunc (ls LdapSSO) CookieDomain() string {\n\treturn ls.Cookie.Domain\n}\n\nfunc (ls LdapSSO) BuildCookie(s string, exp time.Time) http.Cookie {\n\tc := http.Cookie{\n\t\tName:     ls.Cookie.Name,\n\t\tValue:    s,\n\t\tDomain:   ls.Cookie.Domain,\n\t\tPath:     \"/\",\n\t\tExpires:  exp,\n\t\tMaxAge:   int(ls.Cookie.ValidHours * 3600),\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t}\n\treturn c\n}\n\nfunc (ls LdapSSO) Logout(expT time.Time) http.Cookie {\n\tc := http.Cookie{\n\t\tName:     ls.Cookie.Name,\n\t\tValue:    \"\",\n\t\tDomain:   ls.Cookie.Domain,\n\t\tPath:     \"/\",\n\t\tExpires:  expT,\n\t\tMaxAge:   -1,\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t}\n\treturn c\n}\n"
  },
  {
    "path": "ldap/ldap_test.go",
    "content": "package ldap\n\nimport (\n\t\"github.com/samitpal/simple-sso/sso\"\n\t\"net/http\"\n\t\"os\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc init() {\n\tos.Setenv(sso.ConfMap[\"sso_private_key_path\"], \"../util/test/test_key.pem\")\n}\n\nfunc TestBuildCookie(t *testing.T) {\n\tos.Setenv(sso.ConfMap[\"sso_cookie_name\"], \"LoginCookie\")\n\tos.Setenv(sso.ConfMap[\"sso_cookie_domain\"], \"test.com\")\n\n\tls, err := NewLdapSSO()\n\tif err != nil {\n\t\tt.Errorf(\"Error: %v\", err)\n\t}\n\n\tcv := \"Cookie Value\"\n\texpTime := time.Now().Add(time.Hour * time.Duration(ls.CTValidHours()))\n\texpectedCookie := http.Cookie{\n\t\tName:     ls.CookieName(),\n\t\tValue:    cv,\n\t\tDomain:   ls.CookieDomain(),\n\t\tPath:     \"/\",\n\t\tExpires:  expTime,\n\t\tMaxAge:   int(ls.CTValidHours() * 3600),\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t}\n\trecCookie := ls.BuildCookie(cv, expTime)\n\tif !reflect.DeepEqual(expectedCookie, recCookie) {\n\t\tt.Errorf(\"Got %v\\n Want: %v\", recCookie, expectedCookie)\n\t}\n\n}\n\nfunc TestLogout(t *testing.T) {\n\tos.Setenv(sso.ConfMap[\"sso_cookie_name\"], \"LoginCookie\")\n\tos.Setenv(sso.ConfMap[\"sso_cookie_domain\"], \"test.com\")\n\n\tls, err := NewLdapSSO()\n\tif err != nil {\n\t\tt.Errorf(\"Error: %v\", err)\n\t}\n\n\texpTime := time.Now().Add(time.Hour * time.Duration(-1))\n\texpectedCookie := http.Cookie{\n\t\tName:     ls.CookieName(),\n\t\tValue:    \"\",\n\t\tDomain:   ls.CookieDomain(),\n\t\tPath:     \"/\",\n\t\tExpires:  expTime,\n\t\tMaxAge:   -1,\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t}\n\n\trecCookie := ls.Logout(expTime)\n\tif !reflect.DeepEqual(expectedCookie, recCookie) {\n\t\tt.Errorf(\"Got: %v\\n Want: %v\", recCookie, expectedCookie)\n\t}\n}\n\nfunc TestCTValidHours(t *testing.T) {\n\tvh := \"30\"\n\tos.Setenv(sso.ConfMap[\"sso_cookie_validhours\"], vh)\n\n\tls, err := NewLdapSSO()\n\tif err != nil {\n\t\tt.Errorf(\"Error: %v\", err)\n\t}\n\n\ti, _ := strconv.Atoi(vh)\n\tif ls.CTValidHours() != int64(i) {\n\t\tt.Errorf(\"Got: %d\\n Want: %d\", ls.CTValidHours(), i)\n\t}\n}\n\nfunc TestCookieName(t *testing.T) {\n\tcn := \"my cookie\"\n\tos.Setenv(sso.ConfMap[\"sso_cookie_name\"], cn)\n\n\tls, err := NewLdapSSO()\n\tif err != nil {\n\t\tt.Errorf(\"Error: %v\", err)\n\t}\n\n\tif ls.CookieName() != cn {\n\t\tt.Errorf(\"Got: %s\\n Want: %s\", ls.CookieName(), cn)\n\t}\n}\n\nfunc TestCookieDomain(t *testing.T) {\n\tdn := \"mydomain.com\"\n\tos.Setenv(sso.ConfMap[\"sso_cookie_domain\"], dn)\n\n\tls, err := NewLdapSSO()\n\tif err != nil {\n\t\tt.Errorf(\"Error: %v\", err)\n\t}\n\n\tif ls.CookieDomain() != dn {\n\t\tt.Errorf(\"Got: %s\\n Want: %s\", ls.CookieDomain(), dn)\n\t}\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\n//go:generate go-bindata templates/...\n\nimport (\n\t\"fmt\"\n\t\"github.com/gorilla/handlers\"\n\t\"github.com/gorilla/mux\"\n\tweblog \"github.com/samitpal/goProbe/log\"\n\t\"html/template\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/samitpal/simple-sso/ldap\"\n\t\"github.com/samitpal/simple-sso/sso\"\n)\n\nvar lsso sso.SSOer\nvar templates = template.New(\"\")\n\nfunc init() {\n\tvar err error\n\tlsso, err = ldap.NewLdapSSO()\n\tif err != nil {\n\t\tlog.Fatalf(\"Error initializing ldap sso: %s\", err)\n\t}\n\n\tfor _, path := range AssetNames() {\n\t\tbytes, err := Asset(path)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to parse: path=%s, err=%s\", path, err)\n\t\t}\n\t\ttemplates.New(path).Parse(string(bytes))\n\t}\n}\n\ntype TmplData struct {\n\tQueryString string\n\tError       bool\n}\n\nfunc renderTemplate(w http.ResponseWriter, tmpl string, p interface{}) {\n\terr := templates.ExecuteTemplate(w, tmpl, p)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t}\n}\n\n// handleSSOGetRequest presents the login form\nfunc handleSSOGetRequest(w http.ResponseWriter, r *http.Request) {\n\terr := false\n\tif r.URL.Query().Get(\"auth_error\") != \"\" {\n\t\terr = true\n\t}\n\ttmplData := TmplData{QueryString: r.URL.Query().Get(\"s_url\"), Error: err}\n\trenderTemplate(w, \"templates/login.html\", &tmplData)\n}\n\n// handleSSOPostRequest sets the sso cookie.\nfunc handleSSOPostRequest(w http.ResponseWriter, r *http.Request) {\n\tr.ParseForm()\n\tp_uri := r.PostFormValue(\"query_string\")\n\n\tu, g, err := lsso.Auth(r.PostFormValue(\"username\"), r.PostFormValue(\"password\"))\n\tif u != nil {\n\t\tvh := lsso.CTValidHours()\n\t\texp := time.Now().Add(time.Hour * time.Duration(vh)).UTC()\n\t\ttok, _ := lsso.BuildJWTToken(*u, *g, exp)\n\t\tc := lsso.BuildCookie(tok, exp)\n\t\thttp.SetCookie(w, &c)\n\t\thttp.Redirect(w, r, p_uri, 301)\n\t\treturn\n\t}\n\tif err != nil {\n\t\tif sso.Err401Map[err] {\n\t\t\tlog.Println(err)\n\t\t\thttp.Redirect(w, r, fmt.Sprintf(\"/sso?s_url=%s&auth_error=true\", p_uri), 301)\n\t\t\treturn\n\t\t}\n\t\tlog.Println(err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\tfmt.Fprintf(w, \"Not able to service this request. Please try again later.\")\n\t\treturn\n\n\t}\n}\n\n// handleAuthTokenRequest generates the raw jwt token and sends it across.\nfunc handleAuthTokenRequest(w http.ResponseWriter, r *http.Request) {\n\tr.ParseForm()\n\tu, g, err := lsso.Auth(r.PostFormValue(\"username\"), r.PostFormValue(\"password\"))\n\tif u != nil {\n\t\ttok, _ := lsso.BuildJWTToken(*u, *g, time.Now().Add(time.Hour*time.Duration(lsso.CTValidHours())).UTC())\n\t\tw.WriteHeader(http.StatusOK)\n\t\tfmt.Fprint(w, tok)\n\t\treturn\n\t}\n\tif err != nil {\n\t\tif sso.Err401Map[err] {\n\t\t\tlog.Println(err)\n\t\t\tfmt.Fprintf(w, \"Unauthorized.\")\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\tfmt.Fprintf(w, \"Not able to service the request. Please try again later.\")\n\t\treturn\n\n\t}\n}\n\n// handleLogoutRequest function invalidates the sso cookie.\nfunc handleLogoutRequest(w http.ResponseWriter, r *http.Request) {\n\texpT := time.Now().Add(time.Hour * time.Duration(-1))\n\tlc := lsso.Logout(expT)\n\n\thttp.SetCookie(w, &lc)\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintf(w, \"You have been logged out.\")\n\treturn\n}\n\n// handleTestRequest function is just for the purpose of testing.\nfunc handleTestRequest(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tfmt.Fprintf(w, \"You have visited a test page.\")\n\treturn\n}\n\nfunc main() {\n\tlog.Println(\"Starting login server.\")\n\tr := mux.NewRouter()\n\n\tvar fh *os.File\n\tvar err error\n\twld := ldap.BaseConf.WeblogDir\n\tif wld != \"\" {\n\t\tfh, err = weblog.SetupWebLog(wld, time.Now())\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to set up logging: %v\", err)\n\t\t}\n\t} else {\n\t\tfh = os.Stdout // logs web accesses to stdout. May not be thread safe.\n\t}\n\n\tr.Handle(\"/sso\", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleSSOPostRequest))).Methods(\"POST\")\n\tr.Handle(\"/sso\", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleSSOGetRequest))).Methods(\"GET\")\n\tr.Handle(\"/logout\", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleLogoutRequest))).Methods(\"GET\")\n\tr.Handle(\"/auth_token\", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleAuthTokenRequest))).Methods(\"POST\")\n\tr.Handle(\"/test\", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleTestRequest))).Methods(\"GET\")\n\n\thttp.Handle(\"/\", r)\n\n\terr = http.ListenAndServeTLS(\":8081\", ldap.BaseConf.SSLCertPath, ldap.BaseConf.SSLKeyPath, nil)\n\tif err != nil {\n\t\tlog.Fatal(\"ListenAndServe: \", err)\n\t}\n}\n"
  },
  {
    "path": "ssl_certs/README.md",
    "content": "This directory contains the ssl certificate and the ssl key. simple-sso runs on https and the cert and the key are passed to  ListenAndServeTLS. "
  },
  {
    "path": "ssl_certs/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICfzCCAegCCQDGlpg4vnJQDjANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC\nSU4xEjAQBgNVBAgMCUtBUk5BVEFLQTESMBAGA1UEBwwJQkFOR0FMT1JFMQ4wDAYD\nVQQKDAVzYW1pdDEOMAwGA1UECwwFc2FtaXQxEjAQBgNVBAMMCTEyNy4wLjAuMTEY\nMBYGCSqGSIb3DQEJARYJYWFhQGcuY29tMB4XDTE2MDgwODA2MTUxMVoXDTE3MDgw\nOTA2MTUxMVowgYMxCzAJBgNVBAYTAklOMRIwEAYDVQQIDAlLQVJOQVRBS0ExEjAQ\nBgNVBAcMCUJBTkdBTE9SRTEOMAwGA1UECgwFc2FtaXQxDjAMBgNVBAsMBXNhbWl0\nMRIwEAYDVQQDDAkxMjcuMC4wLjExGDAWBgkqhkiG9w0BCQEWCWFhYUBnLmNvbTCB\nnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2Mo/2ICS+hK5yo73ckX88w2TPUwT\n8dLqna4XA1At6KjNbCCWce9UwRLAwj+bHNXKKMKtBP6acG9FZxdKYPcT3+qrnh/O\n40wq/j6yB/ON3wQaGzLkIhr/3nrf/AeG9g47Gxrg6jXdSHmH3RNif3MjRD3X76HL\n46+yb0C7bUDJwXcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBKtrEIUN+iUDs5CBdz\n0tdwfBa9Vq2ympprGOqwDwYsqFDz95SSPHg96R8WsnO/AL27oGDsc3pL3WFh4UZI\nT+lunFNXfm+gfDB/w5N63lO6WTCnmLmUKsHK0HDKbzUyuTPTiQ9owqMGiolo5KJY\nbqRppccEsPF07d1iUQkhTfrIKQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "ssl_certs/key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQDYyj/YgJL6ErnKjvdyRfzzDZM9TBPx0uqdrhcDUC3oqM1sIJZx\n71TBEsDCP5sc1coowq0E/ppwb0VnF0pg9xPf6queH87jTCr+PrIH843fBBobMuQi\nGv/eet/8B4b2DjsbGuDqNd1IeYfdE2J/cyNEPdfvocvjr7JvQLttQMnBdwIDAQAB\nAoGAYYbjIBf/hwbjlE+q3DrGJ+XEhn/yPQkgyRznd3MbpB5Eg89JPypnG5C/LOQG\nePtovduOkL+lZM16EH221VZyFqauNt/V5w24ufMQZ1xuJ4WgOeGssZNA7qb+XbqI\np0Mck5CDmJO97F6zxQsaOFX7bxX4mx93CC1o7RHAY+daxykCQQD4YRh/gEztP2F1\nzVcqSPaxbfxCkBFiAgDyf1WXfHCL86zOMzbR9okOvVWgQcLEivhRKg6Vd/qcyjmQ\nxq3jQ/fDAkEA33EI6YqEqSlizTtTV25TYI8AoJ0c7DMMo3Ns1fDtB4DT/mH+PkGf\najv0+lHGT0Z3ugIRf55wgcmmcnoESMXoPQJAGjb9Q+/BrsSiv7E1gvQCfYWTO19D\nRmnZub5wxTVQF6VXVsgXACAaJSEcmXZ3XREh1kcvFN196PB7FOmzTqpMywJBAMTX\naJmNTRdVfVP+CorAh7VN5aiZIKy4wE6SVfQXnkj45kl4/KjN2OmWzldjiQe3tavp\nPI8n/kdoZTj+Yx3VM6UCQDVKlhN6gp+3bW9TUT/eBN0vR0GKBmExzAmNcBRGJjLB\nSDbx5tNKi0xhBIECGGWwpuZTZMCoh5LtJpD2z7BmiHA=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "sso/sso.go",
    "content": "package sso\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n)\n\nvar (\n\tErrUnAuthorized = errors.New(\"Not Authorized\")\n\tErrUserNotFound = errors.New(\"User Not Found\")\n)\n\n// SSOImplementer is what it needs to be implemented for sso functionality.\ntype SSOer interface {\n\t// Auth takes user,password strings as arguments and returns the user, user roles (e.g ldap groups)\n\t// (string slice) if the call succeds. Auth should return the ErrUnAuthorized or ErrUserNotFound error if\n\t// auth fails or if the user is not found respectively.\n\tAuth(string, string) (*string, *[]string, error)\n\t// CTValidHours returns the cookie/jwt token validity in hours.\n\tCTValidHours() int64\n\tCookieName() string\n\tCookieDomain() string\n\t// BuildJWTToken takes the user and the user roles info which is then signed by the private\n\t// key of the login server. The expiry of the token is set per the third argument.\n\tBuildJWTToken(string, []string, time.Time) (string, error)\n\t// BuildCookie takes the jwt token and returns a cookie and sets the expiration time of the same to that of\n\t// the second arg.\n\tBuildCookie(string, time.Time) http.Cookie\n\t// Logout sets the expiration time of the cookie in the past rendering it unusable.\n\tLogout(time.Time) http.Cookie\n}\n\nvar Err401Map = map[error]bool{\n\tErrUnAuthorized: true,\n\tErrUserNotFound: true,\n}\n\n// All environment variables config goes here for better tracking.\nvar ConfMap = map[string]string{\n\t// ssl certs.\n\t\"sso_ssl_cert_path\": \"sso_ssl_cert_path\",\n\t\"sso_ssl_key_path\":  \"sso_ssl_key_path\",\n\t// private key path for signing the jwt.\n\t\"sso_private_key_path\": \"sso_private_key_path\",\n\t// weblog dir path\n\t\"sso_weblog_dir\": \"sso_weblog_dir\",\n\t// User roles for authorization, (true/false)\n\t\"sso_user_roles\": \"sso_user_roles\",\n\t// cookie configs.\n\t\"sso_cookie_name\":       \"sso_cookie_name\",\n\t\"sso_cookie_domain\":     \"sso_cookie_domain\",\n\t\"sso_cookie_validhours\": \"sso_cookie_validhours\",\n\t// ldap configs. This should go into the respective package.\n\t\"sso_ldap_host\":       \"sso_ldap_host\",\n\t\"sso_ldap_port\":       \"sso_ldap_port\",\n\t\"sso_ldap_ssl\":        \"sso_ldap_ssl\",\n\t\"sso_ldap_basedn\":     \"sso_ldap_basedn\",\n\t\"sso_ldap_binddn\":     \"sso_ldap_binddn\",\n\t\"sso_ldap_bindpasswd\": \"sso_ldap_bindpasswd\",\n}\n\n// setDefaultString returns a given default string.\nfunc setDefaultString(s string, d string) string {\n\tif s == \"\" {\n\t\treturn d\n\t}\n\treturn s\n}\n\ntype BaseConfig struct {\n\tSSLCertPath    string\n\tSSLKeyPath     string\n\tPrivateKeyPath string\n\tWeblogDir      string\n\tUserRoles      bool\n}\n\n// SetupBaseConfig function setups some generic configs\nfunc SetupBaseConfig() (*BaseConfig, error) {\n\tsslCertPath := setDefaultString(os.Getenv(ConfMap[\"sso_ssl_cert_path\"]), \"ssl_certs/cert.pem\")\n\tsslKeyPath := setDefaultString(os.Getenv(ConfMap[\"sso_ssl_key_path\"]), \"ssl_certs/key.pem\")\n\tprivateKeyPath := setDefaultString(os.Getenv(ConfMap[\"sso_private_key_path\"]), \"key_pair/demo.rsa\")\n\tweblogDir := setDefaultString(os.Getenv(ConfMap[\"sso_weblog_dir\"]), \"\")\n\tuserRoles, err := strconv.ParseBool(setDefaultString(os.Getenv(ConfMap[\"sso_user_roles\"]), \"false\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &BaseConfig{sslCertPath, sslKeyPath, privateKeyPath, weblogDir, userRoles}, nil\n}\n\ntype CookieConfig struct {\n\tName       string\n\tDomain     string\n\tValidHours int64\n}\n\n// SetupCookieConfig sets up cookie config.\nfunc SetupCookieConfig() (*CookieConfig, error) {\n\tname := setDefaultString(os.Getenv(ConfMap[\"sso_cookie_name\"]), \"SSO_C\")\n\tdomain := setDefaultString(os.Getenv(ConfMap[\"sso_cookie_domain\"]), \"127.0.0.1\")\n\tvalidHours, err := strconv.Atoi(setDefaultString(os.Getenv(ConfMap[\"sso_cookie_validhours\"]), \"20\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &CookieConfig{name, domain, int64(validHours)}, nil\n}\n"
  },
  {
    "path": "sso/sso_test.go",
    "content": "package sso\n\nimport (\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestSetupDefaultString(t *testing.T) {\n\ts := \"return_me\"\n\td := \"not_me\"\n\tr := setDefaultString(s, d)\n\tif r != s {\n\t\tt.Errorf(\"Got: %s Want: %s\", r, s)\n\t}\n\n\ts1 := \"\"\n\td1 := \"return_me\"\n\tr = setDefaultString(s1, d1)\n\tif r != d1 {\n\t\tt.Errorf(\"Got: %s Want: %s\", r, d1)\n\t}\n}\n\nfunc TestSetupBaseConfig(t *testing.T) {\n\texpBaseConfig := BaseConfig{\n\t\t\"ssl_certs/cert.pem\",\n\t\t\"ssl_certs/key.pem\",\n\t\t\"key_pair/demo.rsa\",\n\t\t\"\",\n\t\tfalse,\n\t}\n\tb, err := SetupBaseConfig()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif !reflect.DeepEqual(expBaseConfig, *b) {\n\t\tt.Errorf(\"Got: %v\\n\\t Want: %v\", *b, expBaseConfig)\n\t}\n\n\texpBaseConfig = BaseConfig{\n\t\t\"ssl_certs/certreal.pem\",\n\t\t\"ssl_certs/keyreal.pem\",\n\t\t\"key_pair/privatereal.rsa\",\n\t\t\"/tmp/weblog\",\n\t\ttrue,\n\t}\n\n\tos.Setenv(ConfMap[\"sso_ssl_cert_path\"], \"ssl_certs/certreal.pem\")\n\tos.Setenv(ConfMap[\"sso_ssl_key_path\"], \"ssl_certs/keyreal.pem\")\n\tos.Setenv(ConfMap[\"sso_private_key_path\"], \"key_pair/privatereal.rsa\")\n\tos.Setenv(ConfMap[\"sso_weblog_dir\"], \"/tmp/weblog\")\n\tos.Setenv(ConfMap[\"sso_user_roles\"], \"true\")\n\tb, err = SetupBaseConfig()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif !reflect.DeepEqual(expBaseConfig, *b) {\n\t\tt.Errorf(\"Got: %v\\n\\t Want: %v\", *b, expBaseConfig)\n\t}\n}\n\nfunc TestSetupCookieConfig(t *testing.T) {\n\texpCookie := CookieConfig{\n\t\t\"SSO_C\",\n\t\t\"127.0.0.1\",\n\t\t20,\n\t}\n\n\tc, err := SetupCookieConfig()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif !reflect.DeepEqual(expCookie, *c) {\n\t\tt.Errorf(\"Got: %v\\n Want: %v\", *c, expCookie)\n\t}\n\n\texpCookie = CookieConfig{\n\t\t\"Cookie\",\n\t\t\"abc.com\",\n\t\t10,\n\t}\n\tos.Setenv((ConfMap[\"sso_cookie_name\"]), \"Cookie\")\n\tos.Setenv((ConfMap[\"sso_cookie_domain\"]), \"abc.com\")\n\tos.Setenv((ConfMap[\"sso_cookie_validhours\"]), \"10\")\n\tc, err = SetupCookieConfig()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif !reflect.DeepEqual(expCookie, *c) {\n\t\tt.Errorf(\"Got: %v\\n Want: %v\", *c, expCookie)\n\t}\n\n}\n"
  },
  {
    "path": "templates/footer.html",
    "content": "{{ define \"footer\" }}\n  </body>\n</html>\n{{ end }}"
  },
  {
    "path": "templates/header.html",
    "content": "{{ define \"header\" }}\n<html>\n  <head>\n    <title>Login</title>\n\n    <style type=\"text/css\">\n\t    .smart-green {\n\t    margin-left:auto;\n\t    margin-right:auto;\n\n\t    max-width: 500px;\n\t    background: #F8F8F8;\n\t    padding: 30px 30px 20px 30px;\n\t    font: 12px Arial, Helvetica, sans-serif;\n\t    color: #666;\n\t    border-radius: 5px;\n\t    -webkit-border-radius: 5px;\n\t    -moz-border-radius: 5px;\n\t}\n\t\t.smart-green h1 {\n\t    font: 24px \"Trebuchet MS\", Arial, Helvetica, sans-serif;\n\t    padding: 20px 0px 20px 40px;\n\t    display: block;\n\t    margin: -30px -30px 10px -30px;\n\t    color: #FFF;\n\t    background: #9DC45F;\n\t    text-shadow: 1px 1px 1px #949494;\n\t    border-radius: 5px 5px 0px 0px;\n\t    -webkit-border-radius: 5px 5px 0px 0px;\n\t    -moz-border-radius: 5px 5px 0px 0px;\n\t    border-bottom:1px solid #89AF4C;\n\n\t}\n\t\t.smart-green h1>span {\n\t    display: block;\n\t    font-size: 15px;\n\t    color: #FFCCCC;\n\t}\n\n\t\t.smart-green label {\n\t    display: block;\n\t    margin: 0px 0px 5px;\n\t}\n\t\t.smart-green label>span {\n\t    float: left;\n\t    margin-top: 10px;\n\t    color: #5E5E5E;\n\t}\n\t\t.smart-green input[type=\"text\"], .smart-green input[type=\"password\"] {\n\t    color: #555;\n\t    height: 30px;\n\t    line-height:15px;\n\t    width: 100%;\n\t    padding: 0px 0px 0px 10px;\n\t    margin-top: 2px;\n\t    border: 1px solid #E5E5E5;\n\t    background: #FBFBFB;\n\t    outline: 0;\n\t    -webkit-box-shadow: inset 1px 1px 2px rgba(238, 238, 238, 0.2);\n\t    box-shadow: inset 1px 1px 2px rgba(238, 238, 238, 0.2);\n\t    font: normal 14px/14px Arial, Helvetica, sans-serif;\n\t}\n\t\t.smart-green .button {\n\t    background-color: #9DC45F;\n\t    border-radius: 5px;\n\t    -webkit-border-radius: 5px;\n\t    -moz-border-border-radius: 5px;\n\t    border: none;\n\t    padding: 10px 25px 10px 25px;\n\t    color: #FFF;\n\t    text-shadow: 1px 1px 1px #949494;\n\t}\n\t\t.smart-green .button:hover {\n\t    background-color:#80A24A;\n\t}\n  </style>\n  </head>\n  <body>\n{{ end }}"
  },
  {
    "path": "templates/login.html",
    "content": "{{ template \"header\" }}\n\n<form action=\"/sso\" method=\"post\" class=\"smart-green\">\n    <h1>SSO Login Form\n    \t{{ if .Error }}\n        \t<span>Invalid credentials.</span>\n    \t{{ end }}\n    </h1>\n    <label>\n        <span>User Name :</span>\n        <input id=\"name\" type=\"text\" name=\"username\" placeholder=\"Your Full Name\" />\n    </label>\n   \n    <label>\n        <span>Password :</span>\n        <input id=\"email\" type=\"password\" name=\"password\" placeholder=\"Valid Password\" />\n    </label> \n     <label>\n     \t<span>&nbsp;</span>\n        <input id=\"query_string\" type=\"hidden\" name=\"query_string\" value={{ .QueryString }} />\n    </label>    \n     <label>\n        <span>&nbsp;</span>\n        <input type=\"submit\" class=\"button\" value=\"Submit\" />\n    </label>    \n</form>\n\n{{ template \"footer\" }}\n"
  },
  {
    "path": "util/test/test_key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAoWydaG49rGhkMw/SfLPhiXZtx1hbzmdLYz0BEiu+hhWH2PCs\nd83J/nymDPDdVaQrT0HQ2v3y0SbYeKvBCViyfSXi6cnpRzNTzQyQfypT79xHc1+i\njwUvpG1Ts6zZwfwJGbKgZsz8EDp2Jna/fPTOnEHOfRhvfRR9H0RUNsLgPiJ1YUH5\noWeE7sQk/l9CGtkhXKsYVbUV3bx1DQw9TTQtgFKT4VQCNJ3KEHowD6Xdcw8Dzwab\njAnPB9zI+7yWWAjZPSNJlAjTpV3rE4Yj1vd8hEuk2wIgV6INLLuVHmShy8YrUkOW\nupyU2So/pRcLwRR+TBXcqsYFpIt+tIAQth7eDwIDAQABAoIBAGioPN3KK54uCFi6\nt2M2VNGEwOPvu4X0noH2uU0Io3vXVb4nPApol7+xHQ9i0n2F9LZsG3cAEn/byZli\n8cKXiRFukNG2oNISyxA0RzLLRKRMkt6QcJp9aEgYwZ3KQVxthZDtqOU9nWcAID4L\n21aueY4BdFjSkOXtdLni2R6v9icRtJWPtsiVBmIk+9UrFmDraDT//rPAe8gClvoC\npCedLPWzTLuDDXMlfFnfK6QZ8iChY5osH/yxEzXats5u+TM2ZLiONvdrRYzq9ioi\n6l+agEyRtT+KjzFTcu707HMzmcXiNkh7C/okLER260GlG3yqGab7kSoE61J7AKpY\npEgH/zECgYEAz3ul2xY9l26BNHfRF+VK5aayNZ+9YL1yX95jA7fJAK7u1ZlMhYBK\nWTdW+1X8spuNlvUX1oKekEiPPW5ViBlDhAIkgC7c5kAMk3+mjaYDYwq5yCRbTx7N\ny51MWBaqMEd2Izn+PKhwKeJkqsiMbWBuAhDSyI7I3pxiELPc4oUIfI0CgYEAxyvM\ndh7WHWqZfnwHvS6FDnYFrzL0OljG7P4ElhO94dBpefczKYXU8a1veQEbbE4wGVw3\nwUKgG8wHLota+940lwqPpC/1M/Je51zGVs4/YDOJJrRQqwxyDELbuifRHY5pGCpK\nHu9cRw0qWCmrsFVQWclPleCL1tfslTd+IqHAlAsCgYAowfdgxEuxFaoX7nmKoiZG\nWqqjUg/Xkx+GqZ71ugKoObT9DLI1f3Aben2BvfB3/Yqg3uCh6OLRIQ/SV3xB0gSr\nR+h3rb0DFg3iY68KIFSF/jNkl4/ASSLQHsRCgaFI/qC8ZsYEkGoIMErqKZ88VTcG\n/NsLPtFCuaGh+lMnxE5YeQKBgQC6wOPPoixsmsbgZdYv2o3iuGGuHJ4Kk7G7CJgu\nTMaQFYbBWTw85AN+tXw/vv0CufG55dFVwm40gkP9raebYYh4U+vKLTnDArFgSYqk\nXHHqd4hTpWG6cUoDGzHCxJD9IMqEYSrtBM3GxZ592lzlU6mq9utMAqe8xOxOIiGA\nwaC8bwKBgCHC8s0pxjsuqBa3uyg8QgiGHS0dnsMV8mvrzf9DkeQ/DnhZjblKltJl\nUFD2glN8EdKr/+cZsMIrQm70SqGa/jAdzwPzGBOoMdDyCM9OmaHGWsgkXqmA9Cr0\nV7JoxLW7cgJztNfu02XS9BE8Ua6qx4rqCLsh8XKmtT1pA/XLPuCD\n-----END RSA PRIVATE KEY-----"
  },
  {
    "path": "util/test/test_key.pub",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoWydaG49rGhkMw/SfLPh\niXZtx1hbzmdLYz0BEiu+hhWH2PCsd83J/nymDPDdVaQrT0HQ2v3y0SbYeKvBCViy\nfSXi6cnpRzNTzQyQfypT79xHc1+ijwUvpG1Ts6zZwfwJGbKgZsz8EDp2Jna/fPTO\nnEHOfRhvfRR9H0RUNsLgPiJ1YUH5oWeE7sQk/l9CGtkhXKsYVbUV3bx1DQw9TTQt\ngFKT4VQCNJ3KEHowD6Xdcw8DzwabjAnPB9zI+7yWWAjZPSNJlAjTpV3rE4Yj1vd8\nhEuk2wIgV6INLLuVHmShy8YrUkOWupyU2So/pRcLwRR+TBXcqsYFpIt+tIAQth7e\nDwIDAQAB\n-----END PUBLIC KEY-----"
  },
  {
    "path": "util/util.go",
    "content": "package util\n\nimport (\n\t\"crypto/rsa\"\n\tjwt \"github.com/dgrijalva/jwt-go\"\n)\n\ntype CustomClaims struct {\n\tUser  string   `json:\"user\"`\n\tRoles []string `json:\"roles\"`\n\tjwt.StandardClaims\n}\n\n// GenJWT generates the jwt token. Among other stuff, it packs in the authenticated user name and the roles that the\n// user belongs to and an expiration time. The info is then signed by the private key of the login server.\nfunc GenJWT(u string, g []string, p *rsa.PrivateKey, t int64) (string, error) {\n\tclaims := CustomClaims{\n\t\tu,\n\t\tg,\n\t\tjwt.StandardClaims{\n\t\t\tExpiresAt: t,\n\t\t\tIssuer:    \"Login_Server\",\n\t\t},\n\t}\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)\n\treturn token.SignedString(p)\n}\n"
  },
  {
    "path": "util/util_test.go",
    "content": "package util \n\nimport (\n\t\"io/ioutil\"\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"testing\"\n)\n\n\nfunc TestGenJWT(t *testing.T) {\n\tkeyData, _ := ioutil.ReadFile(\"test/test_key.pem\")\n\tkey, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)\t\n\n\tsignature := \"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdF9hY2NudCIsInJvbGVzIjpbInJvbGUxIiwicm9sZTIiXSwiZXhwIjoxMjM0LCJpc3MiOiJMb2dpbl9TZXJ2ZXIifQ.hYZ38sZjFUenWVlMlimDxd2z8M1LFTR9zs8_O9RGnxM8n0UJO8GGn12qY2-XrBCv2BLIh2bJXvCee2hDSZO8F9jvKXXMJyYoEtABYrA5MSYm33J1BfcWYsBqKAIFKiTtDrns297OX9nkLyt4_q3J7qUU8EjE6d1Xhc_vqvL-FVjlETwuAqbUBlkRdb_5yNQ03bNzVi7lvIOMEQ4qyOWw3DkudFDGTRQqaHuYT0MgKWU5A_CyEYSOsuIO6ZI77gQyFOrkc2vM1kSo9xPVEoF_34A5w1TWuySJ6c7Sc7JiSOWA5zrTsX6TavvejhfbTeqK5MTfD4AD9wBS_gVeSgdp7Q\"\n\tu := \"test_accnt\"\n\tr := []string{\"role1\", \"role2\"}\n\n\ts, err := GenJWT(u, r, key, 1234)\n\tif err !=nil {\n\t\tt.Errorf(\"Error: %v\", err)\n\t}\n\n\tif signature != s {\n\t\tt.Errorf(\"Got: %s\\n Want: %s\", s, signature)\n\t}\n}"
  }
]