Full Code of samitpal/simple-sso for AI

master 72eb3da9990a cached
24 files
37.1 KB
14.0k tokens
46 symbols
1 requests
Download .txt
Repository: samitpal/simple-sso
Branch: master
Commit: 72eb3da9990a
Files: 24
Total size: 37.1 KB

Directory structure:
gitextract_hkivw28_/

├── .travis.yml
├── LICENSE.md
├── README.md
├── example_app/
│   └── main.go
├── key_pair/
│   ├── README.md
│   ├── demo.rsa
│   └── demo.rsa.pub
├── ldap/
│   ├── config.go
│   ├── config_test.go
│   ├── ldap.go
│   └── ldap_test.go
├── main.go
├── ssl_certs/
│   ├── README.md
│   ├── cert.pem
│   └── key.pem
├── sso/
│   ├── sso.go
│   └── sso_test.go
├── templates/
│   ├── footer.html
│   ├── header.html
│   └── login.html
└── util/
    ├── test/
    │   ├── test_key.pem
    │   └── test_key.pub
    ├── util.go
    └── util_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .travis.yml
================================================
sudo: false
language: go

install:
 - go get -u github.com/jteeuwen/go-bindata/...
 - go get -u github.com/dgrijalva/jwt-go/...
 - go get -u github.com/gorilla/mux/...
 - go get -u gopkg.in/ldap.v2/...
 - go get -u github.com/gorilla/handlers/...
 - go get -u github.com/samitpal/goProbe/...

before_script:
 - go generate
go:
 - 1.5

script:
 - go vet ./...
 - go test ./...


================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) [2016] [Samit Pal]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
[![Build Status](https://travis-ci.org/samitpal/simple-sso.svg?branch=master)](https://travis-ci.org/samitpal/simple-sso)

[google group](https://groups.google.com/forum/#!forum/simple-sso)

Summary
------------------
simple-sso is an SSO service with support for roles based authorization written in the Go programming language. 

For 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.

simple-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.

simple-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..

They say a picture is thousand times more effective, so here is a diagram which shows traffic flow with simple-sso.

![alt tag](https://docs.google.com/drawings/d/1blQbqjT4lb0nu_lX-WO2OaQPvhg5I2pF0LvPZnQ9ywA/pub?w=960&h=720)

Installation
-------------------
##### To build from source follow the steps below: 

```sh
$ go get -u github.com/jteeuwen/go-bindata/...

$ go get -u github.com/samitpal/simple-sso/...

$ export PATH=$PATH:$GOPATH/bin

$ go generate

$ go install
```

Running the binary
-------------------

Just run the simple-sso binary. Following principles of 12 factor app, simple-sso uses environment variables for its configurations. These are.

| Variable      | Default value | Purpose |
|---------------|--------------|------------|
| sso_ssl_cert_path  |  ssl_certs/cert.pem | ssl certificate path. |
| sso_ssl_key_path  |ssl_certs/key.pem   | ssl certificate private key. |
| sso_private_key_path  | key_pair/demo.rsa  | rsa private key path used to sign the token. |
| sso_weblog_dir  |  - | Directory path where access hits are logged. |
| sso_user_roles  | false  | Whether to pack in the roles info within the token. |
| sso_cookie_name  | SSO_C  | Name of the sso cookie. |
| sso_cookie_domain  | 127.0.0.1  | Domain name of the cookie. |
| sso_cookie_validhours  | 20  | Cookie validity in hours. |
| sso_ldap_host  | localhost  | Ldap host. |
| sso_ldap_port  | 389  | Ldap host port. |
| sso_ldap_ssl  | false  | whether to use ssl. |
| sso_ldap_basedn  | - | Ldap base dn. |
| sso_ldap_binddn  | - | Ldap bind dn if anonymous bind is disallowed. |
| sso_ldap_bindpasswd  | - | Ldap bind password if anonymous bind is disallowed. |


Caveats
------------------
* Since time is of essence in this infrastructure, the server time needs to be set and managed correctly.
* Communication between this service and the ldap infrastruture should be encrypted.
* This has been tested with openldap.

================================================
FILE: example_app/main.go
================================================
package main

import (
	"crypto/rsa"
	"fmt"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gorilla/mux"
	"github.com/samitpal/simple-sso/util"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

var parsedPubKey *rsa.PublicKey

func init() {
	key, _ := ioutil.ReadFile("../key_pair/demo.rsa.pub") // this is the public key of the login (simple-sso) server
	parsedPubKey, _ = jwt.ParseRSAPublicKeyFromPEM(key)
}

func cookieCheck(w http.ResponseWriter, r *http.Request) {
	c, err := r.Cookie("SSO_C")
	if err == http.ErrNoCookie {
		// we redirect to the login service setting the appropriate s_url to come back after auth.
		http.Redirect(w, r, "https://127.0.0.1:8081/sso?s_url=https://127.0.0.1:8082/cookie", 301)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	parts := strings.Split(strings.Split(c.String(), "=")[1], ".")
	err = jwt.SigningMethodRS512.Verify(strings.Join(parts[0:2], "."), parts[2], parsedPubKey)
	if err != nil {
		log.Fatalf("[%v] Error while verifying key: %v", strings.Split(c.String(), "=")[1], err)
	}

	tokenString := strings.Split(c.String(), "=")[1]
	token, err := jwt.ParseWithClaims(tokenString, &util.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return parsedPubKey, nil
	})

	claims, ok := token.Claims.(*util.CustomClaims) // claims.User and claims.Roles are what we are interested in.
	if ok && token.Valid {
		fmt.Printf("User: %v Roles: %v Tok_Expires: %v \n", claims.User, claims.Roles, claims.StandardClaims.ExpiresAt)

	} else {
		fmt.Println(err)
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "You have visited a cookietest page.\n\n")
	fmt.Fprintf(w, "User: %v, Roles: %v, Tok_Expires: %v\n", claims.User, claims.Roles, claims.StandardClaims.ExpiresAt)
	return
}

func authTokCheck(w http.ResponseWriter, r *http.Request) {
	h := r.Header.Get("Authorization")
	if h == "" {
		http.Error(w, "No Authorization header", http.StatusInternalServerError)
		return
	}
	parts := strings.Split(strings.Split(h, " ")[1], ".")
	err := jwt.SigningMethodRS512.Verify(strings.Join(parts[0:2], "."), parts[2], parsedPubKey)
	if err != nil {
		log.Fatalf("[%v] Error while verifying key: %v", strings.Split(h, "=")[1], err)
	}

	tokenString := strings.Split(h, " ")[1]
	token, err := jwt.ParseWithClaims(tokenString, &util.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return parsedPubKey, nil
	})

	claims, ok := token.Claims.(*util.CustomClaims) // claims.User and claims.Roles are what we are interested in.
	if ok && token.Valid {
		fmt.Printf("User: %v Roles: %v Tok_Expires: %v \n", claims.User, claims.Roles, claims.StandardClaims.ExpiresAt)

	} else {
		fmt.Println(err)
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "You have visited a auth tokentest page.\n\n")
	fmt.Fprintf(w, "User: %v, Roles: %v, Tok_Expires: %v\n", claims.User, claims.Roles, claims.StandardClaims.ExpiresAt)
	return
}
func main() {
	log.Println("Starting app server.")
	r := mux.NewRouter()
	r.HandleFunc("/cookie", cookieCheck)
	r.HandleFunc("/auth_token", authTokCheck)

	http.Handle("/", r)
	err := http.ListenAndServeTLS(":8082", "../ssl_certs/cert.pem", "../ssl_certs/key.pem", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}


================================================
FILE: key_pair/README.md
================================================
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
to decrypt the same

================================================
FILE: key_pair/demo.rsa
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA1YOCGREepbXPOou0dLcZy8XXp4jD1If6fVFAYx/sa72/ih+B
NGkxCuq20sLqoPQb273bixUPwWtjqWJCaHzf5WgPwh2zej7V3FiwnMTDuAPLdo+6
0FWhEUSb9pZlnoxXIQWjUabixahmpyJVBdavbReBNQr3xkeio87vDSpSzI9qsfGl
SkA2mm14GIgnBGaNEwxka+YTLZgZOMtNMw38zW+t2PVmy5ul+pFXMvG87FN4fiSu
dQdIeS7YyWGUEuCV/091neOpp4oWdwAkik/+oVE4VcIUvgvsmtV6z3KNYUcYfRoC
EWaBoe8I78hy+nssDUKlT7XBN56hIXek6P9TewIDAQABAoIBAFnAj0a0QJrOA0+L
/I53jZtwDgg54IANrQlSx2sjt0FPIR4RwkFi2p/JLJMKJpEELFXByHD9qILY/qrs
SBgeLgwEI2OpEpIXqdSXX552xAMtbTDomFINPMjCe4E7lXoBanrSIOYo7fjComwt
bWon5dRI5iKC+sbZxA9x5GE3YljkR5lAu01RFbc0iDThNsBJYAFxsLgOmLgBF8xO
frmZt+CkgfNsFq1PguX3AoL7es6kwWtn2yLlIqxp/QOsuNuFnjo+hQ7WnaHyKqBF
r/dbF58qXxvSnGWzVsz29hYglNWyTSPDUOwWeES14qGZsal9KUGSubd9MKNC3Dks
T0aMdxECgYEA+x7m2Bt04RKLqtE5Oh5L9RhANZ7/B8qfgFIjKv7EJ2ovqTWUCVsy
ljHuSo2YN7kQ1/NfTGiOzv6JRdeNtwCqbBYU619KyhICVanUBz4i+6rtqQPhhp9U
WuXRp1rjE3haxGBPkY+Ze58mMiMGLvOtjFA+5jze6AWiXB8ksz7olgMCgYEA2amL
rwvxlPV8M+knBtRkIc7nCWZ6Lz2sL8VXHvqWQpvY6XVMXOo+OJxXGzw5LMo4th4q
qEPxyuUpMuGt+3SYtD2w2+nL6bLNutmj1lSAW0Rac1tW7kH08RLTRaQhy/qbB5s/
pi1M24h0yPCuRmm+EhXeuOReP62seFc5lJw8bykCgYEA4Nye4MxVMGUm42JN2Bjg
8ysv89PXkeaCRKlIDGvswU54NxBe6rHa7lrvgZqgvuTcjELFBuppVjjeOsf1gfT6
paZwPQMrOR4/MO3Nil69fJVmEn4DKETriClaPn1H8FtJC6ciGLl5OhUcYrCyDMDu
mkIQ0KGZCDJjXBIXDto58nkCgYEAmeX5L+GgBJS2JvYZdAjEa+shDFJ63eAbWQON
IAhKKfqLmjYnsiKlr91K8aTZQEQTaSFXQ/YWhkEVqjZLj9nXBsn/vN5IIYsdT5oG
78p7nwxrb9kLVBcqmzGWVE1C4DjnWK96h4LMLwUCnfkfIAYwMBVqjwxZX2jq44O4
4My/JlECgYEAwIrodHWjWhY1h5R9N3NXCKP9QPuevJWgvkZjhXDo+nPZE7T37yMe
I+Y1+yXGEKP0d56uhfnRg2GaIBOE+KMdC/mxRNYyhuIQRIro/N3brbILp1nK8Gpo
PaBcKoBjqxclV7sDeHfzCCB4GQTAVa2HFPpYvLVRo8hSiR4bdSBcjVo=
-----END RSA PRIVATE KEY-----


================================================
FILE: key_pair/demo.rsa.pub
================================================
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1YOCGREepbXPOou0dLcZ
y8XXp4jD1If6fVFAYx/sa72/ih+BNGkxCuq20sLqoPQb273bixUPwWtjqWJCaHzf
5WgPwh2zej7V3FiwnMTDuAPLdo+60FWhEUSb9pZlnoxXIQWjUabixahmpyJVBdav
bReBNQr3xkeio87vDSpSzI9qsfGlSkA2mm14GIgnBGaNEwxka+YTLZgZOMtNMw38
zW+t2PVmy5ul+pFXMvG87FN4fiSudQdIeS7YyWGUEuCV/091neOpp4oWdwAkik/+
oVE4VcIUvgvsmtV6z3KNYUcYfRoCEWaBoe8I78hy+nssDUKlT7XBN56hIXek6P9T
ewIDAQAB
-----END PUBLIC KEY-----


================================================
FILE: ldap/config.go
================================================
package ldap

import (
	"crypto/rsa"
	"errors"
	jwt "github.com/dgrijalva/jwt-go"
	"io/ioutil"
	"log"
	"os"
	"strconv"

	"github.com/samitpal/simple-sso/sso"
)

var PrivateKey *rsa.PrivateKey
var BaseConf *sso.BaseConfig

type LdapConfig struct {
	host       string
	port       int
	ssl        bool
	basedn     string
	binddn     string
	bindPasswd string
}

func setupBaseConfig() {
	var err error
	BaseConf, err = sso.SetupBaseConfig()
	if err != nil {
		log.Fatal(err)
	}
	privateKeyData, err := ioutil.ReadFile(BaseConf.PrivateKeyPath)
	if err != nil {
		log.Fatal(err)
	}
	PrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyData)
	if err != nil {
		log.Fatal(err)
	}
}

// setDefaultString returns a given default string.
func setDefaultString(s string, d string) string {
	if s == "" {
		return d
	}
	return s
}

// ldapConfig sets up ldap config from the env.
func (l *LdapConfig) setupLdapConfig() error {

	l.host = setDefaultString(os.Getenv(sso.ConfMap["sso_ldap_host"]), "localhost")

	port, err := strconv.Atoi(setDefaultString(os.Getenv(sso.ConfMap["sso_ldap_port"]), "389"))
	if err != nil {
		return err
	}
	l.port = port

	ssl, err := strconv.ParseBool(setDefaultString(os.Getenv(sso.ConfMap["sso_ldap_ssl"]), "false"))
	if err != nil {
		return err
	}
	l.ssl = ssl

	l.basedn = os.Getenv(sso.ConfMap["sso_ldap_basedn"])
	l.binddn = os.Getenv(sso.ConfMap["sso_ldap_binddn"])

	l.bindPasswd = os.Getenv(sso.ConfMap["sso_ldap_bindpasswd"])

	if l.binddn != "" && l.bindPasswd == "" {
		return errors.New("Bind dn is set but bind password is not set.")
	}

	return nil
}


================================================
FILE: ldap/config_test.go
================================================
package ldap

import (
	"github.com/samitpal/simple-sso/sso"
	"os"
	"reflect"
	"testing"
)

func TestSetupDefaultString(t *testing.T) {
	s := "return_me"
	d := "not_me"
	r := setDefaultString(s, d)
	if r != s {
		t.Errorf("Got: %s Want: %s", r, s)
	}

	s1 := ""
	d1 := "return_me"
	r = setDefaultString(s1, d1)
	if r != d1 {
		t.Errorf("Got: %s Want: %s", r, d1)
	}
}

func TestSetupLdapConfig(t *testing.T) {
	os.Setenv(sso.ConfMap["sso_ldap_host"], "host")
	os.Setenv(sso.ConfMap["sso_ldap_port"], "123")
	os.Setenv(sso.ConfMap["sso_ldap_ssl"], "true")
	os.Setenv(sso.ConfMap["sso_ldap_basedn"], "basedn")
	os.Setenv(sso.ConfMap["sso_ldap_binddn"], "binddn")
	os.Setenv(sso.ConfMap["sso_ldap_bindpasswd"], "bindpasswd")
	l := LdapConfig{}
	err := l.setupLdapConfig()
	if err != nil {
		t.Errorf("Error: %v", err)
	}
	w := LdapConfig{"host", 123, true, "basedn", "binddn", "bindpasswd"}
	if !reflect.DeepEqual(l, w) {
		t.Errorf("Got: %v\n \tWant: %v", l, w)
	}

	_ = os.Unsetenv(sso.ConfMap["sso_ldap_host"])
	_ = os.Unsetenv(sso.ConfMap["sso_ldap_port"])
	_ = os.Unsetenv(sso.ConfMap["sso_ldap_ssl"])
	err = l.setupLdapConfig()
	if err != nil {
		t.Errorf("Error: %v", err)
	}
	w = LdapConfig{"localhost", 389, false, "basedn", "binddn", "bindpasswd"}
	if !reflect.DeepEqual(l, w) {
		t.Errorf("Got: %v\n \tWant: %v", l, w)
	}

}


================================================
FILE: ldap/ldap.go
================================================
// package ldap is an sso implementation. It uses an ldap backend to authenticate and optionally
// utilize ldap group memberships for setting up roles in the cookie/jwt which can later be used
// by applications for authorization.
package ldap

import (
	"crypto/tls"
	"fmt"
	"gopkg.in/ldap.v2"
	"net/http"
	"time"

	"github.com/samitpal/simple-sso/sso"
	"github.com/samitpal/simple-sso/util"
)

type LdapSSO struct {
	Cookie *sso.CookieConfig
	Ldap   *LdapConfig
}

var (
	ErrUserNotFound = sso.ErrUserNotFound
	ErrUnauthorized = sso.ErrUnAuthorized
)

func NewLdapSSO() (*LdapSSO, error) {
	setupBaseConfig()
	c, err := sso.SetupCookieConfig()
	if err != nil {
		return nil, err
	}

	l := new(LdapConfig)
	err = l.setupLdapConfig()
	if err != nil {
		return nil, err
	}

	return &LdapSSO{c, l}, nil
}

func (ls LdapSSO) Auth(u string, p string) (*string, *[]string, error) {

	ldap.DefaultTimeout = 20 * time.Second // applies to Dial and DialTLS methods.
	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Ldap.host, ls.Ldap.port))
	if err != nil {
		return nil, nil, err
	}
	defer l.Close()

	// Reconnect with TLS if sso_ldap_ssl env is set.
	if ls.Ldap.ssl {
		err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
		if err != nil {
			return nil, nil, err
		}
	}

	// First bind with a read only user
	if ls.Ldap.binddn != "" {
		err = l.Bind(ls.Ldap.binddn, ls.Ldap.bindPasswd)
		if err != nil {
			return nil, nil, err
		}
	}

	// Search for the given username
	searchRequestUser := ldap.NewSearchRequest(
		ls.Ldap.basedn,
		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 30, false, // sets a time limit of 30 secs
		fmt.Sprintf("(&(objectClass=inetOrgPerson)(uid=%s))", u),
		[]string{"dn"},
		nil,
	)

	sru, err := l.Search(searchRequestUser)
	if err != nil {
		return nil, nil, err
	}

	if len(sru.Entries) != 1 {
		return nil, nil, ErrUserNotFound
	}

	userdn := sru.Entries[0].DN

	// Bind as the user to verify their password
	err = l.Bind(userdn, p)
	if err != nil {
		return nil, nil, ErrUnauthorized
	}

	// Now find the group membership (if sso_user_roles env is true).
	var g []string
	if BaseConf.UserRoles {
		searchRequestGroups := ldap.NewSearchRequest(
			ls.Ldap.basedn,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 30, false, // sets a time limit of 30 secs
			fmt.Sprintf("(&(objectClass=posixGroup)(memberUid=%s))", u),
			[]string{"cn"},
			nil,
		)
		srg, err := l.Search(searchRequestGroups)
		if err != nil {
			return &u, nil, err
		}

		g = srg.Entries[0].GetAttributeValues("cn")
	}

	return &u, &g, nil
}

func (ls LdapSSO) CTValidHours() int64 {
	return ls.Cookie.ValidHours
}

func (ls LdapSSO) BuildJWTToken(u string, g []string, exp time.Time) (string, error) {
	return util.GenJWT(u, g, PrivateKey, exp.Unix())

}

func (ls LdapSSO) CookieName() string {
	return ls.Cookie.Name
}

func (ls LdapSSO) CookieDomain() string {
	return ls.Cookie.Domain
}

func (ls LdapSSO) BuildCookie(s string, exp time.Time) http.Cookie {
	c := http.Cookie{
		Name:     ls.Cookie.Name,
		Value:    s,
		Domain:   ls.Cookie.Domain,
		Path:     "/",
		Expires:  exp,
		MaxAge:   int(ls.Cookie.ValidHours * 3600),
		Secure:   true,
		HttpOnly: true,
	}
	return c
}

func (ls LdapSSO) Logout(expT time.Time) http.Cookie {
	c := http.Cookie{
		Name:     ls.Cookie.Name,
		Value:    "",
		Domain:   ls.Cookie.Domain,
		Path:     "/",
		Expires:  expT,
		MaxAge:   -1,
		Secure:   true,
		HttpOnly: true,
	}
	return c
}


================================================
FILE: ldap/ldap_test.go
================================================
package ldap

import (
	"github.com/samitpal/simple-sso/sso"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"testing"
	"time"
)

func init() {
	os.Setenv(sso.ConfMap["sso_private_key_path"], "../util/test/test_key.pem")
}

func TestBuildCookie(t *testing.T) {
	os.Setenv(sso.ConfMap["sso_cookie_name"], "LoginCookie")
	os.Setenv(sso.ConfMap["sso_cookie_domain"], "test.com")

	ls, err := NewLdapSSO()
	if err != nil {
		t.Errorf("Error: %v", err)
	}

	cv := "Cookie Value"
	expTime := time.Now().Add(time.Hour * time.Duration(ls.CTValidHours()))
	expectedCookie := http.Cookie{
		Name:     ls.CookieName(),
		Value:    cv,
		Domain:   ls.CookieDomain(),
		Path:     "/",
		Expires:  expTime,
		MaxAge:   int(ls.CTValidHours() * 3600),
		Secure:   true,
		HttpOnly: true,
	}
	recCookie := ls.BuildCookie(cv, expTime)
	if !reflect.DeepEqual(expectedCookie, recCookie) {
		t.Errorf("Got %v\n Want: %v", recCookie, expectedCookie)
	}

}

func TestLogout(t *testing.T) {
	os.Setenv(sso.ConfMap["sso_cookie_name"], "LoginCookie")
	os.Setenv(sso.ConfMap["sso_cookie_domain"], "test.com")

	ls, err := NewLdapSSO()
	if err != nil {
		t.Errorf("Error: %v", err)
	}

	expTime := time.Now().Add(time.Hour * time.Duration(-1))
	expectedCookie := http.Cookie{
		Name:     ls.CookieName(),
		Value:    "",
		Domain:   ls.CookieDomain(),
		Path:     "/",
		Expires:  expTime,
		MaxAge:   -1,
		Secure:   true,
		HttpOnly: true,
	}

	recCookie := ls.Logout(expTime)
	if !reflect.DeepEqual(expectedCookie, recCookie) {
		t.Errorf("Got: %v\n Want: %v", recCookie, expectedCookie)
	}
}

func TestCTValidHours(t *testing.T) {
	vh := "30"
	os.Setenv(sso.ConfMap["sso_cookie_validhours"], vh)

	ls, err := NewLdapSSO()
	if err != nil {
		t.Errorf("Error: %v", err)
	}

	i, _ := strconv.Atoi(vh)
	if ls.CTValidHours() != int64(i) {
		t.Errorf("Got: %d\n Want: %d", ls.CTValidHours(), i)
	}
}

func TestCookieName(t *testing.T) {
	cn := "my cookie"
	os.Setenv(sso.ConfMap["sso_cookie_name"], cn)

	ls, err := NewLdapSSO()
	if err != nil {
		t.Errorf("Error: %v", err)
	}

	if ls.CookieName() != cn {
		t.Errorf("Got: %s\n Want: %s", ls.CookieName(), cn)
	}
}

func TestCookieDomain(t *testing.T) {
	dn := "mydomain.com"
	os.Setenv(sso.ConfMap["sso_cookie_domain"], dn)

	ls, err := NewLdapSSO()
	if err != nil {
		t.Errorf("Error: %v", err)
	}

	if ls.CookieDomain() != dn {
		t.Errorf("Got: %s\n Want: %s", ls.CookieDomain(), dn)
	}
}


================================================
FILE: main.go
================================================
package main

//go:generate go-bindata templates/...

import (
	"fmt"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	weblog "github.com/samitpal/goProbe/log"
	"html/template"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/samitpal/simple-sso/ldap"
	"github.com/samitpal/simple-sso/sso"
)

var lsso sso.SSOer
var templates = template.New("")

func init() {
	var err error
	lsso, err = ldap.NewLdapSSO()
	if err != nil {
		log.Fatalf("Error initializing ldap sso: %s", err)
	}

	for _, path := range AssetNames() {
		bytes, err := Asset(path)
		if err != nil {
			log.Fatalf("Unable to parse: path=%s, err=%s", path, err)
		}
		templates.New(path).Parse(string(bytes))
	}
}

type TmplData struct {
	QueryString string
	Error       bool
}

func renderTemplate(w http.ResponseWriter, tmpl string, p interface{}) {
	err := templates.ExecuteTemplate(w, tmpl, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

// handleSSOGetRequest presents the login form
func handleSSOGetRequest(w http.ResponseWriter, r *http.Request) {
	err := false
	if r.URL.Query().Get("auth_error") != "" {
		err = true
	}
	tmplData := TmplData{QueryString: r.URL.Query().Get("s_url"), Error: err}
	renderTemplate(w, "templates/login.html", &tmplData)
}

// handleSSOPostRequest sets the sso cookie.
func handleSSOPostRequest(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	p_uri := r.PostFormValue("query_string")

	u, g, err := lsso.Auth(r.PostFormValue("username"), r.PostFormValue("password"))
	if u != nil {
		vh := lsso.CTValidHours()
		exp := time.Now().Add(time.Hour * time.Duration(vh)).UTC()
		tok, _ := lsso.BuildJWTToken(*u, *g, exp)
		c := lsso.BuildCookie(tok, exp)
		http.SetCookie(w, &c)
		http.Redirect(w, r, p_uri, 301)
		return
	}
	if err != nil {
		if sso.Err401Map[err] {
			log.Println(err)
			http.Redirect(w, r, fmt.Sprintf("/sso?s_url=%s&auth_error=true", p_uri), 301)
			return
		}
		log.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "Not able to service this request. Please try again later.")
		return

	}
}

// handleAuthTokenRequest generates the raw jwt token and sends it across.
func handleAuthTokenRequest(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	u, g, err := lsso.Auth(r.PostFormValue("username"), r.PostFormValue("password"))
	if u != nil {
		tok, _ := lsso.BuildJWTToken(*u, *g, time.Now().Add(time.Hour*time.Duration(lsso.CTValidHours())).UTC())
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, tok)
		return
	}
	if err != nil {
		if sso.Err401Map[err] {
			log.Println(err)
			fmt.Fprintf(w, "Unauthorized.")
			return
		}
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "Not able to service the request. Please try again later.")
		return

	}
}

// handleLogoutRequest function invalidates the sso cookie.
func handleLogoutRequest(w http.ResponseWriter, r *http.Request) {
	expT := time.Now().Add(time.Hour * time.Duration(-1))
	lc := lsso.Logout(expT)

	http.SetCookie(w, &lc)
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "You have been logged out.")
	return
}

// handleTestRequest function is just for the purpose of testing.
func handleTestRequest(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "You have visited a test page.")
	return
}

func main() {
	log.Println("Starting login server.")
	r := mux.NewRouter()

	var fh *os.File
	var err error
	wld := ldap.BaseConf.WeblogDir
	if wld != "" {
		fh, err = weblog.SetupWebLog(wld, time.Now())
		if err != nil {
			log.Fatalf("Failed to set up logging: %v", err)
		}
	} else {
		fh = os.Stdout // logs web accesses to stdout. May not be thread safe.
	}

	r.Handle("/sso", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleSSOPostRequest))).Methods("POST")
	r.Handle("/sso", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleSSOGetRequest))).Methods("GET")
	r.Handle("/logout", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleLogoutRequest))).Methods("GET")
	r.Handle("/auth_token", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleAuthTokenRequest))).Methods("POST")
	r.Handle("/test", handlers.CombinedLoggingHandler(fh, http.HandlerFunc(handleTestRequest))).Methods("GET")

	http.Handle("/", r)

	err = http.ListenAndServeTLS(":8081", ldap.BaseConf.SSLCertPath, ldap.BaseConf.SSLKeyPath, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}


================================================
FILE: ssl_certs/README.md
================================================
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. 

================================================
FILE: ssl_certs/cert.pem
================================================
-----BEGIN CERTIFICATE-----
MIICfzCCAegCCQDGlpg4vnJQDjANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC
SU4xEjAQBgNVBAgMCUtBUk5BVEFLQTESMBAGA1UEBwwJQkFOR0FMT1JFMQ4wDAYD
VQQKDAVzYW1pdDEOMAwGA1UECwwFc2FtaXQxEjAQBgNVBAMMCTEyNy4wLjAuMTEY
MBYGCSqGSIb3DQEJARYJYWFhQGcuY29tMB4XDTE2MDgwODA2MTUxMVoXDTE3MDgw
OTA2MTUxMVowgYMxCzAJBgNVBAYTAklOMRIwEAYDVQQIDAlLQVJOQVRBS0ExEjAQ
BgNVBAcMCUJBTkdBTE9SRTEOMAwGA1UECgwFc2FtaXQxDjAMBgNVBAsMBXNhbWl0
MRIwEAYDVQQDDAkxMjcuMC4wLjExGDAWBgkqhkiG9w0BCQEWCWFhYUBnLmNvbTCB
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2Mo/2ICS+hK5yo73ckX88w2TPUwT
8dLqna4XA1At6KjNbCCWce9UwRLAwj+bHNXKKMKtBP6acG9FZxdKYPcT3+qrnh/O
40wq/j6yB/ON3wQaGzLkIhr/3nrf/AeG9g47Gxrg6jXdSHmH3RNif3MjRD3X76HL
46+yb0C7bUDJwXcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBKtrEIUN+iUDs5CBdz
0tdwfBa9Vq2ympprGOqwDwYsqFDz95SSPHg96R8WsnO/AL27oGDsc3pL3WFh4UZI
T+lunFNXfm+gfDB/w5N63lO6WTCnmLmUKsHK0HDKbzUyuTPTiQ9owqMGiolo5KJY
bqRppccEsPF07d1iUQkhTfrIKQ==
-----END CERTIFICATE-----


================================================
FILE: ssl_certs/key.pem
================================================
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDYyj/YgJL6ErnKjvdyRfzzDZM9TBPx0uqdrhcDUC3oqM1sIJZx
71TBEsDCP5sc1coowq0E/ppwb0VnF0pg9xPf6queH87jTCr+PrIH843fBBobMuQi
Gv/eet/8B4b2DjsbGuDqNd1IeYfdE2J/cyNEPdfvocvjr7JvQLttQMnBdwIDAQAB
AoGAYYbjIBf/hwbjlE+q3DrGJ+XEhn/yPQkgyRznd3MbpB5Eg89JPypnG5C/LOQG
ePtovduOkL+lZM16EH221VZyFqauNt/V5w24ufMQZ1xuJ4WgOeGssZNA7qb+XbqI
p0Mck5CDmJO97F6zxQsaOFX7bxX4mx93CC1o7RHAY+daxykCQQD4YRh/gEztP2F1
zVcqSPaxbfxCkBFiAgDyf1WXfHCL86zOMzbR9okOvVWgQcLEivhRKg6Vd/qcyjmQ
xq3jQ/fDAkEA33EI6YqEqSlizTtTV25TYI8AoJ0c7DMMo3Ns1fDtB4DT/mH+PkGf
ajv0+lHGT0Z3ugIRf55wgcmmcnoESMXoPQJAGjb9Q+/BrsSiv7E1gvQCfYWTO19D
RmnZub5wxTVQF6VXVsgXACAaJSEcmXZ3XREh1kcvFN196PB7FOmzTqpMywJBAMTX
aJmNTRdVfVP+CorAh7VN5aiZIKy4wE6SVfQXnkj45kl4/KjN2OmWzldjiQe3tavp
PI8n/kdoZTj+Yx3VM6UCQDVKlhN6gp+3bW9TUT/eBN0vR0GKBmExzAmNcBRGJjLB
SDbx5tNKi0xhBIECGGWwpuZTZMCoh5LtJpD2z7BmiHA=
-----END RSA PRIVATE KEY-----


================================================
FILE: sso/sso.go
================================================
package sso

import (
	"errors"
	"net/http"
	"os"
	"strconv"
	"time"
)

var (
	ErrUnAuthorized = errors.New("Not Authorized")
	ErrUserNotFound = errors.New("User Not Found")
)

// SSOImplementer is what it needs to be implemented for sso functionality.
type SSOer interface {
	// Auth takes user,password strings as arguments and returns the user, user roles (e.g ldap groups)
	// (string slice) if the call succeds. Auth should return the ErrUnAuthorized or ErrUserNotFound error if
	// auth fails or if the user is not found respectively.
	Auth(string, string) (*string, *[]string, error)
	// CTValidHours returns the cookie/jwt token validity in hours.
	CTValidHours() int64
	CookieName() string
	CookieDomain() string
	// BuildJWTToken takes the user and the user roles info which is then signed by the private
	// key of the login server. The expiry of the token is set per the third argument.
	BuildJWTToken(string, []string, time.Time) (string, error)
	// BuildCookie takes the jwt token and returns a cookie and sets the expiration time of the same to that of
	// the second arg.
	BuildCookie(string, time.Time) http.Cookie
	// Logout sets the expiration time of the cookie in the past rendering it unusable.
	Logout(time.Time) http.Cookie
}

var Err401Map = map[error]bool{
	ErrUnAuthorized: true,
	ErrUserNotFound: true,
}

// All environment variables config goes here for better tracking.
var ConfMap = map[string]string{
	// ssl certs.
	"sso_ssl_cert_path": "sso_ssl_cert_path",
	"sso_ssl_key_path":  "sso_ssl_key_path",
	// private key path for signing the jwt.
	"sso_private_key_path": "sso_private_key_path",
	// weblog dir path
	"sso_weblog_dir": "sso_weblog_dir",
	// User roles for authorization, (true/false)
	"sso_user_roles": "sso_user_roles",
	// cookie configs.
	"sso_cookie_name":       "sso_cookie_name",
	"sso_cookie_domain":     "sso_cookie_domain",
	"sso_cookie_validhours": "sso_cookie_validhours",
	// ldap configs. This should go into the respective package.
	"sso_ldap_host":       "sso_ldap_host",
	"sso_ldap_port":       "sso_ldap_port",
	"sso_ldap_ssl":        "sso_ldap_ssl",
	"sso_ldap_basedn":     "sso_ldap_basedn",
	"sso_ldap_binddn":     "sso_ldap_binddn",
	"sso_ldap_bindpasswd": "sso_ldap_bindpasswd",
}

// setDefaultString returns a given default string.
func setDefaultString(s string, d string) string {
	if s == "" {
		return d
	}
	return s
}

type BaseConfig struct {
	SSLCertPath    string
	SSLKeyPath     string
	PrivateKeyPath string
	WeblogDir      string
	UserRoles      bool
}

// SetupBaseConfig function setups some generic configs
func SetupBaseConfig() (*BaseConfig, error) {
	sslCertPath := setDefaultString(os.Getenv(ConfMap["sso_ssl_cert_path"]), "ssl_certs/cert.pem")
	sslKeyPath := setDefaultString(os.Getenv(ConfMap["sso_ssl_key_path"]), "ssl_certs/key.pem")
	privateKeyPath := setDefaultString(os.Getenv(ConfMap["sso_private_key_path"]), "key_pair/demo.rsa")
	weblogDir := setDefaultString(os.Getenv(ConfMap["sso_weblog_dir"]), "")
	userRoles, err := strconv.ParseBool(setDefaultString(os.Getenv(ConfMap["sso_user_roles"]), "false"))
	if err != nil {
		return nil, err
	}
	return &BaseConfig{sslCertPath, sslKeyPath, privateKeyPath, weblogDir, userRoles}, nil
}

type CookieConfig struct {
	Name       string
	Domain     string
	ValidHours int64
}

// SetupCookieConfig sets up cookie config.
func SetupCookieConfig() (*CookieConfig, error) {
	name := setDefaultString(os.Getenv(ConfMap["sso_cookie_name"]), "SSO_C")
	domain := setDefaultString(os.Getenv(ConfMap["sso_cookie_domain"]), "127.0.0.1")
	validHours, err := strconv.Atoi(setDefaultString(os.Getenv(ConfMap["sso_cookie_validhours"]), "20"))
	if err != nil {
		return nil, err
	}
	return &CookieConfig{name, domain, int64(validHours)}, nil
}


================================================
FILE: sso/sso_test.go
================================================
package sso

import (
	"os"
	"reflect"
	"testing"
)

func TestSetupDefaultString(t *testing.T) {
	s := "return_me"
	d := "not_me"
	r := setDefaultString(s, d)
	if r != s {
		t.Errorf("Got: %s Want: %s", r, s)
	}

	s1 := ""
	d1 := "return_me"
	r = setDefaultString(s1, d1)
	if r != d1 {
		t.Errorf("Got: %s Want: %s", r, d1)
	}
}

func TestSetupBaseConfig(t *testing.T) {
	expBaseConfig := BaseConfig{
		"ssl_certs/cert.pem",
		"ssl_certs/key.pem",
		"key_pair/demo.rsa",
		"",
		false,
	}
	b, err := SetupBaseConfig()
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(expBaseConfig, *b) {
		t.Errorf("Got: %v\n\t Want: %v", *b, expBaseConfig)
	}

	expBaseConfig = BaseConfig{
		"ssl_certs/certreal.pem",
		"ssl_certs/keyreal.pem",
		"key_pair/privatereal.rsa",
		"/tmp/weblog",
		true,
	}

	os.Setenv(ConfMap["sso_ssl_cert_path"], "ssl_certs/certreal.pem")
	os.Setenv(ConfMap["sso_ssl_key_path"], "ssl_certs/keyreal.pem")
	os.Setenv(ConfMap["sso_private_key_path"], "key_pair/privatereal.rsa")
	os.Setenv(ConfMap["sso_weblog_dir"], "/tmp/weblog")
	os.Setenv(ConfMap["sso_user_roles"], "true")
	b, err = SetupBaseConfig()
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(expBaseConfig, *b) {
		t.Errorf("Got: %v\n\t Want: %v", *b, expBaseConfig)
	}
}

func TestSetupCookieConfig(t *testing.T) {
	expCookie := CookieConfig{
		"SSO_C",
		"127.0.0.1",
		20,
	}

	c, err := SetupCookieConfig()
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(expCookie, *c) {
		t.Errorf("Got: %v\n Want: %v", *c, expCookie)
	}

	expCookie = CookieConfig{
		"Cookie",
		"abc.com",
		10,
	}
	os.Setenv((ConfMap["sso_cookie_name"]), "Cookie")
	os.Setenv((ConfMap["sso_cookie_domain"]), "abc.com")
	os.Setenv((ConfMap["sso_cookie_validhours"]), "10")
	c, err = SetupCookieConfig()
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(expCookie, *c) {
		t.Errorf("Got: %v\n Want: %v", *c, expCookie)
	}

}


================================================
FILE: templates/footer.html
================================================
{{ define "footer" }}
  </body>
</html>
{{ end }}

================================================
FILE: templates/header.html
================================================
{{ define "header" }}
<html>
  <head>
    <title>Login</title>

    <style type="text/css">
	    .smart-green {
	    margin-left:auto;
	    margin-right:auto;

	    max-width: 500px;
	    background: #F8F8F8;
	    padding: 30px 30px 20px 30px;
	    font: 12px Arial, Helvetica, sans-serif;
	    color: #666;
	    border-radius: 5px;
	    -webkit-border-radius: 5px;
	    -moz-border-radius: 5px;
	}
		.smart-green h1 {
	    font: 24px "Trebuchet MS", Arial, Helvetica, sans-serif;
	    padding: 20px 0px 20px 40px;
	    display: block;
	    margin: -30px -30px 10px -30px;
	    color: #FFF;
	    background: #9DC45F;
	    text-shadow: 1px 1px 1px #949494;
	    border-radius: 5px 5px 0px 0px;
	    -webkit-border-radius: 5px 5px 0px 0px;
	    -moz-border-radius: 5px 5px 0px 0px;
	    border-bottom:1px solid #89AF4C;

	}
		.smart-green h1>span {
	    display: block;
	    font-size: 15px;
	    color: #FFCCCC;
	}

		.smart-green label {
	    display: block;
	    margin: 0px 0px 5px;
	}
		.smart-green label>span {
	    float: left;
	    margin-top: 10px;
	    color: #5E5E5E;
	}
		.smart-green input[type="text"], .smart-green input[type="password"] {
	    color: #555;
	    height: 30px;
	    line-height:15px;
	    width: 100%;
	    padding: 0px 0px 0px 10px;
	    margin-top: 2px;
	    border: 1px solid #E5E5E5;
	    background: #FBFBFB;
	    outline: 0;
	    -webkit-box-shadow: inset 1px 1px 2px rgba(238, 238, 238, 0.2);
	    box-shadow: inset 1px 1px 2px rgba(238, 238, 238, 0.2);
	    font: normal 14px/14px Arial, Helvetica, sans-serif;
	}
		.smart-green .button {
	    background-color: #9DC45F;
	    border-radius: 5px;
	    -webkit-border-radius: 5px;
	    -moz-border-border-radius: 5px;
	    border: none;
	    padding: 10px 25px 10px 25px;
	    color: #FFF;
	    text-shadow: 1px 1px 1px #949494;
	}
		.smart-green .button:hover {
	    background-color:#80A24A;
	}
  </style>
  </head>
  <body>
{{ end }}

================================================
FILE: templates/login.html
================================================
{{ template "header" }}

<form action="/sso" method="post" class="smart-green">
    <h1>SSO Login Form
    	{{ if .Error }}
        	<span>Invalid credentials.</span>
    	{{ end }}
    </h1>
    <label>
        <span>User Name :</span>
        <input id="name" type="text" name="username" placeholder="Your Full Name" />
    </label>
   
    <label>
        <span>Password :</span>
        <input id="email" type="password" name="password" placeholder="Valid Password" />
    </label> 
     <label>
     	<span>&nbsp;</span>
        <input id="query_string" type="hidden" name="query_string" value={{ .QueryString }} />
    </label>    
     <label>
        <span>&nbsp;</span>
        <input type="submit" class="button" value="Submit" />
    </label>    
</form>

{{ template "footer" }}


================================================
FILE: util/test/test_key.pem
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAoWydaG49rGhkMw/SfLPhiXZtx1hbzmdLYz0BEiu+hhWH2PCs
d83J/nymDPDdVaQrT0HQ2v3y0SbYeKvBCViyfSXi6cnpRzNTzQyQfypT79xHc1+i
jwUvpG1Ts6zZwfwJGbKgZsz8EDp2Jna/fPTOnEHOfRhvfRR9H0RUNsLgPiJ1YUH5
oWeE7sQk/l9CGtkhXKsYVbUV3bx1DQw9TTQtgFKT4VQCNJ3KEHowD6Xdcw8Dzwab
jAnPB9zI+7yWWAjZPSNJlAjTpV3rE4Yj1vd8hEuk2wIgV6INLLuVHmShy8YrUkOW
upyU2So/pRcLwRR+TBXcqsYFpIt+tIAQth7eDwIDAQABAoIBAGioPN3KK54uCFi6
t2M2VNGEwOPvu4X0noH2uU0Io3vXVb4nPApol7+xHQ9i0n2F9LZsG3cAEn/byZli
8cKXiRFukNG2oNISyxA0RzLLRKRMkt6QcJp9aEgYwZ3KQVxthZDtqOU9nWcAID4L
21aueY4BdFjSkOXtdLni2R6v9icRtJWPtsiVBmIk+9UrFmDraDT//rPAe8gClvoC
pCedLPWzTLuDDXMlfFnfK6QZ8iChY5osH/yxEzXats5u+TM2ZLiONvdrRYzq9ioi
6l+agEyRtT+KjzFTcu707HMzmcXiNkh7C/okLER260GlG3yqGab7kSoE61J7AKpY
pEgH/zECgYEAz3ul2xY9l26BNHfRF+VK5aayNZ+9YL1yX95jA7fJAK7u1ZlMhYBK
WTdW+1X8spuNlvUX1oKekEiPPW5ViBlDhAIkgC7c5kAMk3+mjaYDYwq5yCRbTx7N
y51MWBaqMEd2Izn+PKhwKeJkqsiMbWBuAhDSyI7I3pxiELPc4oUIfI0CgYEAxyvM
dh7WHWqZfnwHvS6FDnYFrzL0OljG7P4ElhO94dBpefczKYXU8a1veQEbbE4wGVw3
wUKgG8wHLota+940lwqPpC/1M/Je51zGVs4/YDOJJrRQqwxyDELbuifRHY5pGCpK
Hu9cRw0qWCmrsFVQWclPleCL1tfslTd+IqHAlAsCgYAowfdgxEuxFaoX7nmKoiZG
WqqjUg/Xkx+GqZ71ugKoObT9DLI1f3Aben2BvfB3/Yqg3uCh6OLRIQ/SV3xB0gSr
R+h3rb0DFg3iY68KIFSF/jNkl4/ASSLQHsRCgaFI/qC8ZsYEkGoIMErqKZ88VTcG
/NsLPtFCuaGh+lMnxE5YeQKBgQC6wOPPoixsmsbgZdYv2o3iuGGuHJ4Kk7G7CJgu
TMaQFYbBWTw85AN+tXw/vv0CufG55dFVwm40gkP9raebYYh4U+vKLTnDArFgSYqk
XHHqd4hTpWG6cUoDGzHCxJD9IMqEYSrtBM3GxZ592lzlU6mq9utMAqe8xOxOIiGA
waC8bwKBgCHC8s0pxjsuqBa3uyg8QgiGHS0dnsMV8mvrzf9DkeQ/DnhZjblKltJl
UFD2glN8EdKr/+cZsMIrQm70SqGa/jAdzwPzGBOoMdDyCM9OmaHGWsgkXqmA9Cr0
V7JoxLW7cgJztNfu02XS9BE8Ua6qx4rqCLsh8XKmtT1pA/XLPuCD
-----END RSA PRIVATE KEY-----

================================================
FILE: util/test/test_key.pub
================================================
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoWydaG49rGhkMw/SfLPh
iXZtx1hbzmdLYz0BEiu+hhWH2PCsd83J/nymDPDdVaQrT0HQ2v3y0SbYeKvBCViy
fSXi6cnpRzNTzQyQfypT79xHc1+ijwUvpG1Ts6zZwfwJGbKgZsz8EDp2Jna/fPTO
nEHOfRhvfRR9H0RUNsLgPiJ1YUH5oWeE7sQk/l9CGtkhXKsYVbUV3bx1DQw9TTQt
gFKT4VQCNJ3KEHowD6Xdcw8DzwabjAnPB9zI+7yWWAjZPSNJlAjTpV3rE4Yj1vd8
hEuk2wIgV6INLLuVHmShy8YrUkOWupyU2So/pRcLwRR+TBXcqsYFpIt+tIAQth7e
DwIDAQAB
-----END PUBLIC KEY-----

================================================
FILE: util/util.go
================================================
package util

import (
	"crypto/rsa"
	jwt "github.com/dgrijalva/jwt-go"
)

type CustomClaims struct {
	User  string   `json:"user"`
	Roles []string `json:"roles"`
	jwt.StandardClaims
}

// GenJWT generates the jwt token. Among other stuff, it packs in the authenticated user name and the roles that the
// user belongs to and an expiration time. The info is then signed by the private key of the login server.
func GenJWT(u string, g []string, p *rsa.PrivateKey, t int64) (string, error) {
	claims := CustomClaims{
		u,
		g,
		jwt.StandardClaims{
			ExpiresAt: t,
			Issuer:    "Login_Server",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
	return token.SignedString(p)
}


================================================
FILE: util/util_test.go
================================================
package util 

import (
	"io/ioutil"
	jwt "github.com/dgrijalva/jwt-go"
	"testing"
)


func TestGenJWT(t *testing.T) {
	keyData, _ := ioutil.ReadFile("test/test_key.pem")
	key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)	

	signature := "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdF9hY2NudCIsInJvbGVzIjpbInJvbGUxIiwicm9sZTIiXSwiZXhwIjoxMjM0LCJpc3MiOiJMb2dpbl9TZXJ2ZXIifQ.hYZ38sZjFUenWVlMlimDxd2z8M1LFTR9zs8_O9RGnxM8n0UJO8GGn12qY2-XrBCv2BLIh2bJXvCee2hDSZO8F9jvKXXMJyYoEtABYrA5MSYm33J1BfcWYsBqKAIFKiTtDrns297OX9nkLyt4_q3J7qUU8EjE6d1Xhc_vqvL-FVjlETwuAqbUBlkRdb_5yNQ03bNzVi7lvIOMEQ4qyOWw3DkudFDGTRQqaHuYT0MgKWU5A_CyEYSOsuIO6ZI77gQyFOrkc2vM1kSo9xPVEoF_34A5w1TWuySJ6c7Sc7JiSOWA5zrTsX6TavvejhfbTeqK5MTfD4AD9wBS_gVeSgdp7Q"
	u := "test_accnt"
	r := []string{"role1", "role2"}

	s, err := GenJWT(u, r, key, 1234)
	if err !=nil {
		t.Errorf("Error: %v", err)
	}

	if signature != s {
		t.Errorf("Got: %s\n Want: %s", s, signature)
	}
}
Download .txt
gitextract_hkivw28_/

├── .travis.yml
├── LICENSE.md
├── README.md
├── example_app/
│   └── main.go
├── key_pair/
│   ├── README.md
│   ├── demo.rsa
│   └── demo.rsa.pub
├── ldap/
│   ├── config.go
│   ├── config_test.go
│   ├── ldap.go
│   └── ldap_test.go
├── main.go
├── ssl_certs/
│   ├── README.md
│   ├── cert.pem
│   └── key.pem
├── sso/
│   ├── sso.go
│   └── sso_test.go
├── templates/
│   ├── footer.html
│   ├── header.html
│   └── login.html
└── util/
    ├── test/
    │   ├── test_key.pem
    │   └── test_key.pub
    ├── util.go
    └── util_test.go
Download .txt
SYMBOL INDEX (46 symbols across 10 files)

FILE: example_app/main.go
  function init (line 17) | func init() {
  function cookieCheck (line 22) | func cookieCheck(w http.ResponseWriter, r *http.Request) {
  function authTokCheck (line 58) | func authTokCheck(w http.ResponseWriter, r *http.Request) {
  function main (line 87) | func main() {

FILE: ldap/config.go
  type LdapConfig (line 18) | type LdapConfig struct
    method setupLdapConfig (line 52) | func (l *LdapConfig) setupLdapConfig() error {
  function setupBaseConfig (line 27) | func setupBaseConfig() {
  function setDefaultString (line 44) | func setDefaultString(s string, d string) string {

FILE: ldap/config_test.go
  function TestSetupDefaultString (line 10) | func TestSetupDefaultString(t *testing.T) {
  function TestSetupLdapConfig (line 26) | func TestSetupLdapConfig(t *testing.T) {

FILE: ldap/ldap.go
  type LdapSSO (line 17) | type LdapSSO struct
    method Auth (line 43) | func (ls LdapSSO) Auth(u string, p string) (*string, *[]string, error) {
    method CTValidHours (line 115) | func (ls LdapSSO) CTValidHours() int64 {
    method BuildJWTToken (line 119) | func (ls LdapSSO) BuildJWTToken(u string, g []string, exp time.Time) (...
    method CookieName (line 124) | func (ls LdapSSO) CookieName() string {
    method CookieDomain (line 128) | func (ls LdapSSO) CookieDomain() string {
    method BuildCookie (line 132) | func (ls LdapSSO) BuildCookie(s string, exp time.Time) http.Cookie {
    method Logout (line 146) | func (ls LdapSSO) Logout(expT time.Time) http.Cookie {
  function NewLdapSSO (line 27) | func NewLdapSSO() (*LdapSSO, error) {

FILE: ldap/ldap_test.go
  function init (line 13) | func init() {
  function TestBuildCookie (line 17) | func TestBuildCookie(t *testing.T) {
  function TestLogout (line 45) | func TestLogout(t *testing.T) {
  function TestCTValidHours (line 72) | func TestCTValidHours(t *testing.T) {
  function TestCookieName (line 87) | func TestCookieName(t *testing.T) {
  function TestCookieDomain (line 101) | func TestCookieDomain(t *testing.T) {

FILE: main.go
  function init (line 23) | func init() {
  type TmplData (line 39) | type TmplData struct
  function renderTemplate (line 44) | func renderTemplate(w http.ResponseWriter, tmpl string, p interface{}) {
  function handleSSOGetRequest (line 52) | func handleSSOGetRequest(w http.ResponseWriter, r *http.Request) {
  function handleSSOPostRequest (line 62) | func handleSSOPostRequest(w http.ResponseWriter, r *http.Request) {
  function handleAuthTokenRequest (line 91) | func handleAuthTokenRequest(w http.ResponseWriter, r *http.Request) {
  function handleLogoutRequest (line 114) | func handleLogoutRequest(w http.ResponseWriter, r *http.Request) {
  function handleTestRequest (line 125) | func handleTestRequest(w http.ResponseWriter, r *http.Request) {
  function main (line 131) | func main() {

FILE: sso/sso.go
  type SSOer (line 17) | type SSOer interface
  function setDefaultString (line 66) | func setDefaultString(s string, d string) string {
  type BaseConfig (line 73) | type BaseConfig struct
  function SetupBaseConfig (line 82) | func SetupBaseConfig() (*BaseConfig, error) {
  type CookieConfig (line 94) | type CookieConfig struct
  function SetupCookieConfig (line 101) | func SetupCookieConfig() (*CookieConfig, error) {

FILE: sso/sso_test.go
  function TestSetupDefaultString (line 9) | func TestSetupDefaultString(t *testing.T) {
  function TestSetupBaseConfig (line 25) | func TestSetupBaseConfig(t *testing.T) {
  function TestSetupCookieConfig (line 63) | func TestSetupCookieConfig(t *testing.T) {

FILE: util/util.go
  type CustomClaims (line 8) | type CustomClaims struct
  function GenJWT (line 16) | func GenJWT(u string, g []string, p *rsa.PrivateKey, t int64) (string, e...

FILE: util/util_test.go
  function TestGenJWT (line 10) | func TestGenJWT(t *testing.T) {
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (42K chars).
[
  {
    "path": ".travis.yml",
    "chars": 376,
    "preview": "sudo: false\nlanguage: go\n\ninstall:\n - go get -u github.com/jteeuwen/go-bindata/...\n - go get -u github.com/dgrijalva/jwt"
  },
  {
    "path": "LICENSE.md",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) [2016] [Samit Pal]\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 3613,
    "preview": "[![Build Status](https://travis-ci.org/samitpal/simple-sso.svg?branch=master)](https://travis-ci.org/samitpal/simple-sso"
  },
  {
    "path": "example_app/main.go",
    "chars": 3251,
    "preview": "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/sa"
  },
  {
    "path": "key_pair/README.md",
    "chars": 160,
    "preview": "This directory contains the rsa key pair. simple-sso uses the private key to sign the jwt tokens. The client apps need t"
  },
  {
    "path": "key_pair/demo.rsa",
    "chars": 1679,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA1YOCGREepbXPOou0dLcZy8XXp4jD1If6fVFAYx/sa72/ih+B\nNGkxCuq20sLqoPQb273bixU"
  },
  {
    "path": "key_pair/demo.rsa.pub",
    "chars": 451,
    "preview": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1YOCGREepbXPOou0dLcZ\ny8XXp4jD1If6fVFAYx/sa72/ih+B"
  },
  {
    "path": "ldap/config.go",
    "chars": 1592,
    "preview": "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"
  },
  {
    "path": "ldap/config_test.go",
    "chars": 1333,
    "preview": "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("
  },
  {
    "path": "ldap/ldap.go",
    "chars": 3454,
    "preview": "// package ldap is an sso implementation. It uses an ldap backend to authenticate and optionally\n// utilize ldap group m"
  },
  {
    "path": "ldap/ldap_test.go",
    "chars": 2406,
    "preview": "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"
  },
  {
    "path": "main.go",
    "chars": 4418,
    "preview": "package main\n\n//go:generate go-bindata templates/...\n\nimport (\n\t\"fmt\"\n\t\"github.com/gorilla/handlers\"\n\t\"github.com/gorill"
  },
  {
    "path": "ssl_certs/README.md",
    "chars": 145,
    "preview": "This directory contains the ssl certificate and the ssl key. simple-sso runs on https and the cert and the key are passe"
  },
  {
    "path": "ssl_certs/cert.pem",
    "chars": 928,
    "preview": "-----BEGIN CERTIFICATE-----\nMIICfzCCAegCCQDGlpg4vnJQDjANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC\nSU4xEjAQBgNVBAgMCUtBUk5BVEF"
  },
  {
    "path": "ssl_certs/key.pem",
    "chars": 887,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQDYyj/YgJL6ErnKjvdyRfzzDZM9TBPx0uqdrhcDUC3oqM1sIJZx\n71TBEsDCP5sc1coowq0E/pp"
  },
  {
    "path": "sso/sso.go",
    "chars": 3768,
    "preview": "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 Authori"
  },
  {
    "path": "sso/sso_test.go",
    "chars": 1911,
    "preview": "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 :="
  },
  {
    "path": "templates/footer.html",
    "chars": 49,
    "preview": "{{ define \"footer\" }}\n  </body>\n</html>\n{{ end }}"
  },
  {
    "path": "templates/header.html",
    "chars": 1922,
    "preview": "{{ define \"header\" }}\n<html>\n  <head>\n    <title>Login</title>\n\n    <style type=\"text/css\">\n\t    .smart-green {\n\t    mar"
  },
  {
    "path": "templates/login.html",
    "chars": 791,
    "preview": "{{ template \"header\" }}\n\n<form action=\"/sso\" method=\"post\" class=\"smart-green\">\n    <h1>SSO Login Form\n    \t{{ if .Error"
  },
  {
    "path": "util/test/test_key.pem",
    "chars": 1674,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAoWydaG49rGhkMw/SfLPhiXZtx1hbzmdLYz0BEiu+hhWH2PCs\nd83J/nymDPDdVaQrT0HQ2v3"
  },
  {
    "path": "util/test/test_key.pub",
    "chars": 450,
    "preview": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoWydaG49rGhkMw/SfLPh\niXZtx1hbzmdLYz0BEiu+hhWH2PCs"
  },
  {
    "path": "util/util.go",
    "chars": 694,
    "preview": "package util\n\nimport (\n\t\"crypto/rsa\"\n\tjwt \"github.com/dgrijalva/jwt-go\"\n)\n\ntype CustomClaims struct {\n\tUser  string   `j"
  },
  {
    "path": "util/util_test.go",
    "chars": 935,
    "preview": "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\t"
  }
]

About this extraction

This page contains the full source code of the samitpal/simple-sso GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (37.1 KB), approximately 14.0k tokens, and a symbol index with 46 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!