<commit_before>package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"time"
)

// Generate a stable SHA256 for a commit.
func createCommitID(c commit) string {
	var b bytes.Buffer
	b.WriteString(fmt.Sprintf("tree %s\n", c.Tree))
	if c.Parent != "" {
		b.WriteString(fmt.Sprintf("parent %s\n", c.Parent))
	}
	b.WriteString(fmt.Sprintf("author %s <%s> %v\n", c.AuthorName, c.AuthorEmail,
		c.Timestamp.Format(time.UnixDate)))
	if c.CommitterEmail != "" {
		b.WriteString(fmt.Sprintf("committer %s <%s> %v\n", c.CommitterName, c.CommitterEmail,
			c.Timestamp.Format(time.UnixDate)))
	}
	b.WriteString("\n" + c.Message)
	b.WriteByte(0)
	s := sha256.Sum256(b.Bytes())
	return hex.EncodeToString(s[:])
}

// Creates the user visible commit text for a commit.
func createCommitText(c commit) (string, error) {
	if c.ID == "" {
		c.ID = createCommitID(c)
	}
	s := fmt.Sprintf("commit %s\n", c.ID)
	s += fmt.Sprintf("Author: %s <%s>\n", c.AuthorName, c.AuthorEmail)
	s += fmt.Sprintf("Date: %v\n\n", c.Timestamp.Format(time.UnixDate))
	if c.Message != "" {
		s += fmt.Sprintf("    %s\n", c.Message)
	}
	return s, nil
}

// Generate the SHA256 for a tree.
// Tree entry structure is:
// * [ type ] [ sha256 ] [ db name ] [ last modified (timestamp) ] [ db size (bytes) ]
func createDBTreeID(entries []dbTreeEntry) string {
	var b bytes.Buffer
	for _, j := range entries {
		b.WriteString(string(j.AType))
		b.WriteByte(0)
		b.WriteString(j.Sha256)
		b.WriteByte(0)
		b.WriteString(j.Name)
		b.WriteByte(0)
		b.WriteString(j.Last_Modified.Format(time.RFC3339))
		b.WriteByte(0)
		b.WriteString(fmt.Sprintf("%d\n", j.Size))
	}
	s := sha256.Sum256(b.Bytes())
	return hex.EncodeToString(s[:])
}

// Check if a database already exists.
func dbExists(dbName string) bool {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the db doesn't exist
		// TODO: Proper handling for errors here.  It may not mean the file doesn't exist.
		return false
	}
	return true
}

// Load the branch heads for a database.
func getBranches(dbName string) (map[string]string, error) {
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "branchHeads"))
	if err != nil {
		return nil, err
	}
	var i map[string]string
	err = json.Unmarshal(b, &i)
	if err != nil {
		log.Printf("Something went wrong unserialising the branchHeads data: %v\n", err.Error())
		return nil, err
	}
	return i, nil
}

// Retrieve the default branch name for a database.
func getDefaultBranchName(dbName string) string {
	if !dbExists(dbName) {
		return "master" // Database doesn't exist, so use "master" as the initial default
	}

	// Return the default branch name
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "defaultBranch"))
	if err != nil {
		log.Printf("Error when reading default branch for '%s': %v\n", dbName, err.Error())
		return "master" // An error occurred reading the default branch name, so default to master
	}
	return string(b[:])
}

// Reads a commit from disk.
func getCommit(id string) (commit, error) {
	var c commit
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		return c, err
	}
	err = json.Unmarshal(b, &c)
	if err != nil {
		log.Printf("Something went wrong unserialising a commit's data: %v\n", err.Error())
		return c, err
	}
	return c, nil
}

// Reads a database from disk.
func getDatabase(id string) ([]byte, error) {
	d, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		log.Printf("Error reading file: '%s': %v\n", id, err.Error())
		return []byte{}, err
	}
	return d, nil
}

// Load the tags (standard, non-annotated type) for a database.
func getTags(dbName string) (map[string]tagEntry, error) {
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "tags"))
	if err != nil {
		_, ok := err.(*os.PathError)
		if ok {
			// There are no tags for the database yet
			return make(map[string]tagEntry), nil
		}

		log.Printf("Something went wrong reading the tags data: %v\n", err.Error())
		return nil, err
	}
	var i map[string]tagEntry
	err = json.Unmarshal(b, &i)
	if err != nil {
		log.Printf("Something went wrong unserialising the tags data: %v\n", err.Error())
		return nil, err
	}
	return i, nil
}

// Reads a tree from disk.
func getTree(id string) (dbTree, error) {
	var t dbTree
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		log.Printf("Error reading file: '%s': %v\n", id, err.Error())
		return t, err
	}
	err = json.Unmarshal(b, &t)
	if err != nil {
		log.Printf("Something went wrong unserialising a commit's data: %v\n", err.Error())
		return t, err
	}
	return t, nil
}

// Returns the list of available databases.
func listDatabases() ([]byte, error) {
	// For now, just use the entries in the "meta" directory as the list
	p := filepath.Join(STORAGEDIR, "meta")
	dirEntries, err := ioutil.ReadDir(p)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the db doesn't exist
		log.Printf("Error when reading database list: %v\n", err)
		return []byte{}, err
	}
	var dbs []string
	for _, j := range dirEntries {
		dbs = append(dbs, j.Name())
	}

	// Convert into json
	j, err := json.MarshalIndent(dbs, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return []byte{}, err
	}

	return j, nil
}

// Store the branch heads for a database.
func storeBranches(dbName string, branches map[string]string) error {
	j, err := json.MarshalIndent(branches, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "branchHeads"), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the branchHeads file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a commit.
func storeCommit(c commit) error {
	j, err := json.MarshalIndent(c, "", " ")
	if err != nil {
		log.Printf("Something went wrong when serialising the commit data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "files", c.ID), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the commit file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a database file.
func storeDatabase(db []byte) error {
	// Create the database file if it doesn't already exist
	a := sha256.Sum256(db)
	sha := hex.EncodeToString(a[:])
	path := filepath.Join(STORAGEDIR, "files", sha)
	f, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the file needs creating
		// TODO: Proper handling for errors here.  It may not mean the file doesn't exist.
		err = ioutil.WriteFile(path, db, os.ModePerm)
		if err != nil {
			log.Printf("Something went wrong writing the database file: %v\n", err.Error())
			return err
		}
		return nil
	}

	// The file already exists.
	// Check if the file size matches the buffer size we're intending on writing, and skip it if so
	// (Obviously this is just a super lightweight check, not a real world approach)
	// TODO: Add real world checks to ensure the file contents are identical.  Maybe read the file contents into
	// TODO  memory, then binary compare them?  Prob not great for memory efficiency, but it would likely do as a
	// TODO  first go for something accurate.
	if len(db) != int(f.Size()) {
		err = ioutil.WriteFile(path, db, os.ModePerm)
		if err != nil {
			log.Printf("Something went wrong writing the database file: %v\n", err.Error())
			return err
		}
	}
	return nil
}

// Stores the default branch name for a database.
func storeDefaultBranchName(dbName string, branchName string) error {
	var buf bytes.Buffer
	buf.WriteString(branchName)
	err := ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "defaultBranch"), buf.Bytes(), os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the default branch name for '%s': %v\n", dbName,
			err.Error())
		return err
	}
	return nil
}

// Store the tags (standard, non-annotated type) for a database.
func storeTags(dbName string, tags map[string]tagEntry) error {
	j, err := json.MarshalIndent(tags, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "tags"), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the tags file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a tree.
func storeTree(t dbTree) error {
	j, err := json.MarshalIndent(t, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the tree data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "files", t.ID), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the tree file: %v\n", err.Error())
		return err
	}
	return nil
}
<commit_msg>meta dir creation code was needed after all ;)<commit_after>package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"time"
)

// Generate a stable SHA256 for a commit.
func createCommitID(c commit) string {
	var b bytes.Buffer
	b.WriteString(fmt.Sprintf("tree %s\n", c.Tree))
	if c.Parent != "" {
		b.WriteString(fmt.Sprintf("parent %s\n", c.Parent))
	}
	b.WriteString(fmt.Sprintf("author %s <%s> %v\n", c.AuthorName, c.AuthorEmail,
		c.Timestamp.Format(time.UnixDate)))
	if c.CommitterEmail != "" {
		b.WriteString(fmt.Sprintf("committer %s <%s> %v\n", c.CommitterName, c.CommitterEmail,
			c.Timestamp.Format(time.UnixDate)))
	}
	b.WriteString("\n" + c.Message)
	b.WriteByte(0)
	s := sha256.Sum256(b.Bytes())
	return hex.EncodeToString(s[:])
}

// Creates the user visible commit text for a commit.
func createCommitText(c commit) (string, error) {
	if c.ID == "" {
		c.ID = createCommitID(c)
	}
	s := fmt.Sprintf("commit %s\n", c.ID)
	s += fmt.Sprintf("Author: %s <%s>\n", c.AuthorName, c.AuthorEmail)
	s += fmt.Sprintf("Date: %v\n\n", c.Timestamp.Format(time.UnixDate))
	if c.Message != "" {
		s += fmt.Sprintf("    %s\n", c.Message)
	}
	return s, nil
}

// Generate the SHA256 for a tree.
// Tree entry structure is:
// * [ type ] [ sha256 ] [ db name ] [ last modified (timestamp) ] [ db size (bytes) ]
func createDBTreeID(entries []dbTreeEntry) string {
	var b bytes.Buffer
	for _, j := range entries {
		b.WriteString(string(j.AType))
		b.WriteByte(0)
		b.WriteString(j.Sha256)
		b.WriteByte(0)
		b.WriteString(j.Name)
		b.WriteByte(0)
		b.WriteString(j.Last_Modified.Format(time.RFC3339))
		b.WriteByte(0)
		b.WriteString(fmt.Sprintf("%d\n", j.Size))
	}
	s := sha256.Sum256(b.Bytes())
	return hex.EncodeToString(s[:])
}

// Check if a database already exists.
func dbExists(dbName string) bool {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the db doesn't exist
		// TODO: Proper handling for errors here.  It may not mean the file doesn't exist.
		return false
	}
	return true
}

// Load the branch heads for a database.
func getBranches(dbName string) (map[string]string, error) {
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "branchHeads"))
	if err != nil {
		return nil, err
	}
	var i map[string]string
	err = json.Unmarshal(b, &i)
	if err != nil {
		log.Printf("Something went wrong unserialising the branchHeads data: %v\n", err.Error())
		return nil, err
	}
	return i, nil
}

// Retrieve the default branch name for a database.
func getDefaultBranchName(dbName string) string {
	if !dbExists(dbName) {
		return "master" // Database doesn't exist, so use "master" as the initial default
	}

	// Return the default branch name
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "defaultBranch"))
	if err != nil {
		log.Printf("Error when reading default branch for '%s': %v\n", dbName, err.Error())
		return "master" // An error occurred reading the default branch name, so default to master
	}
	return string(b[:])
}

// Reads a commit from disk.
func getCommit(id string) (commit, error) {
	var c commit
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		return c, err
	}
	err = json.Unmarshal(b, &c)
	if err != nil {
		log.Printf("Something went wrong unserialising a commit's data: %v\n", err.Error())
		return c, err
	}
	return c, nil
}

// Reads a database from disk.
func getDatabase(id string) ([]byte, error) {
	d, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		log.Printf("Error reading file: '%s': %v\n", id, err.Error())
		return []byte{}, err
	}
	return d, nil
}

// Load the tags (standard, non-annotated type) for a database.
func getTags(dbName string) (map[string]tagEntry, error) {
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "tags"))
	if err != nil {
		_, ok := err.(*os.PathError)
		if ok {
			// There are no tags for the database yet
			return make(map[string]tagEntry), nil
		}

		log.Printf("Something went wrong reading the tags data: %v\n", err.Error())
		return nil, err
	}
	var i map[string]tagEntry
	err = json.Unmarshal(b, &i)
	if err != nil {
		log.Printf("Something went wrong unserialising the tags data: %v\n", err.Error())
		return nil, err
	}
	return i, nil
}

// Reads a tree from disk.
func getTree(id string) (dbTree, error) {
	var t dbTree
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		log.Printf("Error reading file: '%s': %v\n", id, err.Error())
		return t, err
	}
	err = json.Unmarshal(b, &t)
	if err != nil {
		log.Printf("Something went wrong unserialising a commit's data: %v\n", err.Error())
		return t, err
	}
	return t, nil
}

// Returns the list of available databases.
func listDatabases() ([]byte, error) {
	// For now, just use the entries in the "meta" directory as the list
	p := filepath.Join(STORAGEDIR, "meta")
	dirEntries, err := ioutil.ReadDir(p)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the db doesn't exist
		log.Printf("Error when reading database list: %v\n", err)
		return []byte{}, err
	}
	var dbs []string
	for _, j := range dirEntries {
		dbs = append(dbs, j.Name())
	}

	// Convert into json
	j, err := json.MarshalIndent(dbs, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return []byte{}, err
	}

	return j, nil
}

// Store the branch heads for a database.
func storeBranches(dbName string, branches map[string]string) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	j, err := json.MarshalIndent(branches, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "branchHeads"), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the branchHeads file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a commit.
func storeCommit(c commit) error {
	j, err := json.MarshalIndent(c, "", " ")
	if err != nil {
		log.Printf("Something went wrong when serialising the commit data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "files", c.ID), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the commit file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a database file.
func storeDatabase(db []byte) error {
	// Create the database file if it doesn't already exist
	a := sha256.Sum256(db)
	sha := hex.EncodeToString(a[:])
	path := filepath.Join(STORAGEDIR, "files", sha)
	f, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the file needs creating
		// TODO: Proper handling for errors here.  It may not mean the file doesn't exist.
		err = ioutil.WriteFile(path, db, os.ModePerm)
		if err != nil {
			log.Printf("Something went wrong writing the database file: %v\n", err.Error())
			return err
		}
		return nil
	}

	// The file already exists.
	// Check if the file size matches the buffer size we're intending on writing, and skip it if so
	// (Obviously this is just a super lightweight check, not a real world approach)
	// TODO: Add real world checks to ensure the file contents are identical.  Maybe read the file contents into
	// TODO  memory, then binary compare them?  Prob not great for memory efficiency, but it would likely do as a
	// TODO  first go for something accurate.
	if len(db) != int(f.Size()) {
		err = ioutil.WriteFile(path, db, os.ModePerm)
		if err != nil {
			log.Printf("Something went wrong writing the database file: %v\n", err.Error())
			return err
		}
	}
	return nil
}

// Stores the default branch name for a database.
func storeDefaultBranchName(dbName string, branchName string) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	var buf bytes.Buffer
	buf.WriteString(branchName)
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "defaultBranch"), buf.Bytes(), os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the default branch name for '%s': %v\n", dbName,
			err.Error())
		return err
	}
	return nil
}

// Store the tags (standard, non-annotated type) for a database.
func storeTags(dbName string, tags map[string]tagEntry) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	j, err := json.MarshalIndent(tags, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "tags"), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the tags file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a tree.
func storeTree(t dbTree) error {
	j, err := json.MarshalIndent(t, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the tree data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "files", t.ID), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the tree file: %v\n", err.Error())
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package report

import (
	"encoding/json"
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/dgraph-io/gru/admin/mail"
	"github.com/dgraph-io/gru/admin/server"
	"github.com/dgraph-io/gru/dgraph"
	"github.com/dgraph-io/gru/x"
	"github.com/gorilla/mux"
)

type option struct {
	Id   string `json:"_uid_"`
	Name string `json:"name"`
}

type uid struct {
	Id string `json:"_uid_"`
}

type que struct {
	Uid      string  `json:"_uid_"`
	Multiple bool    `json:"multiple,string"`
	Negative float64 `json:"negative,string"`
	Positive float64 `json:"positive,string"`
	Text     string
	Name     string
	Tags     []option `json:"question.tag"`
	Options  []option `json:"question.option"`
	Correct  []option `json:"question.correct"`
}

type questions []cq

func (q questions) Len() int {
	return len(q)
}

func (q questions) Less(i, j int) bool {
	return q[i].Asked.Before(q[j].Asked)
}

func (q questions) Swap(i, j int) {
	q[i], q[j] = q[j], q[i]
}

type cq struct {
	Answer   string    `json:"candidate.answer"`
	Score    float64   `json:"candidate.score,string"`
	Asked    time.Time `json:"question.asked,string"`
	Answered time.Time `json:"question.answered,string"`
	Question []que     `json:"question.uid"`
}

type quiz struct {
	Id       string `json:"_uid_"`
	Duration string `json:"duration"`
	Name     string `json:"name"`
}

type candidates struct {
	Id       string `json:"_uid_"`
	Name     string
	Email    string
	Country  string
	Feedback string
	// Used to calculate percentile, repesents number of candidates having score
	// <= current candidate.
	Idx         int
	Score       float64 `json:",string"`
	CandidateQn []cq    `json:"candidate.question"`
	Complete    bool    `json:"complete,string"`
	Quiz        []quiz  `json:"candidate.quiz"`
	TotalScore  float64
}

type report struct {
	Candidates []candidates `json:"candidate"`
}

func reportQuery(id string) string {
	return `query {
                candidate(_uid_:` + id + `) {
                        _uid_
                        name
                        email
                        country
                        feedback
                        score
                        complete
                        candidate.quiz {
                                _uid_
                                name
                                duration
                        }
                        candidate.question {
                                question.uid {
                                        _uid_
                                        text
                                        name
                                        positive
                                        negative
                                        question.tag {
                                                _uid_
                                                name
                                        }
                                        question.option {
                                                _uid_
                                                name
                                        }
                                        question.correct {
                                                _uid_
                                        }
                                        multiple
                                }
                                question.asked
                                question.answered
                                candidate.answer
                                candidate.score
                        }
                }
        }`
}

type question struct {
	Name      string   `json:"name"`
	Multiple  bool     `json:"multiple"`
	Text      string   `json:"text"`
	TimeTaken string   `json:"time_taken"`
	Positive  float64  `json:"positive"`
	Score     float64  `json:"score"`
	Options   []option `json:"options"`
	Correct   []string `json:"correct"`
	Answers   []string `json:"answers"`
	Answered  bool
	Tags      []string `json:"tags"`
}

type Summary struct {
	Id         string
	Name       string  `json:"name"`
	Email      string  `json:"email"`
	Percentile float64 `json:"percentile"`
	Country    string  `json:"country"`
	QuizName   string
	Feedback   string     `json:"feedback"`
	TimeTaken  string     `json:"time_taken"`
	TotalScore float64    `json:"total_score"`
	MaxScore   float64    `json:"max_score"`
	Questions  []question `json:"questions"`
	Ip         string
}

func uids(opts []option) []string {
	var ids []string
	for _, opt := range opts {
		ids = append(ids, opt.Id)
	}
	return ids
}

func names(opts []option) []string {
	var n []string
	for _, opt := range opts {
		n = append(n, opt.Name)
	}
	return n
}

type ReportError struct {
	Err  string
	Msg  string
	code int
}

type quizRes struct {
	Quiz []struct {
		Candidates []candidates `json:"quiz.candidate"`
	} `json:"quiz"`
}

// ByScore implements sort.Interface for []candidates based on
// the Score field.
type ByScore []candidates

func (a ByScore) Len() int           { return len(a) }
func (a ByScore) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByScore) Less(i, j int) bool { return a[i].Score > a[j].Score }

func percentile(quizId string, cid string) (float64, error) {
	q := `{
	quiz(_uid_: ` + quizId + `) {
		quiz.candidate {
			_uid_
			complete
			score
		}
	}
}
`
	var res quizRes
	if err := dgraph.QueryAndUnmarshal(q, &res); err != nil {
		return 0.0, err
	}

	candidates := res.Quiz[0].Candidates
	i := 0
	for _, cand := range candidates {
		if cand.Complete == true {
			// Lets retain only the candidates who have completed the test
			// for percentile calculation.
			candidates[i] = cand
			i++
		}
	}
	candidates = candidates[:i]

	sort.Sort(ByScore(candidates))
	lastScore := 0.0
	lastIdx := len(candidates)
	for idx, cand := range candidates {
		if cand.Score != lastScore {
			cand.Idx = len(candidates) - idx
			lastIdx = len(candidates) - idx
			lastScore = cand.Score
		} else {
			cand.Idx = lastIdx
		}
		if cand.Id == cid {
			return float64(cand.Idx) / float64(len(candidates)) * 100, nil
		}
	}
	return 0.0, nil
}

func ReportSummary(cid string) (Summary, ReportError) {
	s := Summary{}
	s.Ip = *mail.Ip
	q := reportQuery(cid)
	var rep report
	if err := dgraph.QueryAndUnmarshal(q, &rep); err != nil {
		return s, ReportError{err.Error(), "", http.StatusInternalServerError}
	}

	if len(rep.Candidates) != 1 || rep.Candidates[0].Id == "" || len(rep.Candidates[0].Quiz) != 1 {
		return s, ReportError{"", "Candidate not found.", http.StatusBadRequest}
	}
	s.Id = rep.Candidates[0].Id

	c := rep.Candidates[0]
	s.Name = c.Name
	s.Email = c.Email
	s.Country = c.Country
	s.Feedback = c.Feedback
	// TODO - Check how to obtain sorted results from Dgraph.
	if len(c.CandidateQn) == 0 {
		return s, ReportError{"", "Candidate hasn't started the test", http.StatusBadRequest}
	}

	sort.Sort(questions(c.CandidateQn))
	if !c.CandidateQn[len(c.CandidateQn)-1].Answered.IsZero() {
		s.TimeTaken = c.CandidateQn[len(c.CandidateQn)-1].Answered.Sub(
			c.CandidateQn[0].Asked).String()
	} else {
		// Incase we didn't record the answered for the last qn, say his
		// browser crashed or he didn't finish answering it.
		dur := c.Quiz[0].Duration
		// TODO - This is a hack because duration is stored as 0h50m0s,
		// Ideally it should be 50m0s.
		d, err := time.ParseDuration(dur)
		if err != nil {
			return s, ReportError{"", "Can't parse quiz duration.",
				http.StatusInternalServerError}
		}
		s.TimeTaken = d.String()
	}
	s.QuizName = c.Quiz[0].Name
	perc, err := percentile(c.Quiz[0].Id, c.Id)
	if err != nil {
		return s, ReportError{"", "Error while calculating percentile", http.StatusInternalServerError}
	}
	s.Percentile = x.ToFixed(perc, 2)

	for _, qn := range c.CandidateQn {
		s.TotalScore += qn.Score
		q := qn.Question[0]
		s.MaxScore += float64(len(q.Correct)) * q.Positive
		answers := strings.Split(qn.Answer, ",")
		sq := question{
			Name:     q.Name,
			Text:     q.Text,
			Options:  q.Options,
			Score:    qn.Score,
			Positive: q.Positive,
			Multiple: q.Multiple,
			Correct:  uids(q.Correct),
			Tags:     names(q.Tags),
			Answers:  answers,
			Answered: len(answers) > 0 && answers[0] != "skip",
		}
		if qn.Answered.IsZero() {
			sq.TimeTaken = "-1"
		} else {
			sq.TimeTaken = qn.Answered.Sub(qn.Asked).String()
		}
		s.Questions = append(s.Questions, sq)
	}
	s.TotalScore = x.ToFixed(s.TotalScore, 2)
	s.MaxScore = x.ToFixed(s.MaxScore, 2)
	return s, ReportError{}
}

func Report(w http.ResponseWriter, r *http.Request) {
	sr := server.Response{}
	vars := mux.Vars(r)
	cid := vars["id"]
	s, re := ReportSummary(cid)
	if re.Msg != "" || re.Err != "" {
		sr.Write(w, re.Err, re.Msg, re.code)
		return
	}

	b, err := json.Marshal(s)
	if err != nil {
		sr.Write(w, err.Error(), "", http.StatusInternalServerError)
		return
	}
	w.Write(b)
}
<commit_msg>Handle time taken in report after quiz duration change.<commit_after>package report

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/dgraph-io/gru/admin/mail"
	"github.com/dgraph-io/gru/admin/server"
	"github.com/dgraph-io/gru/dgraph"
	"github.com/dgraph-io/gru/x"
	"github.com/gorilla/mux"
)

type option struct {
	Id   string `json:"_uid_"`
	Name string `json:"name"`
}

type uid struct {
	Id string `json:"_uid_"`
}

type que struct {
	Uid      string  `json:"_uid_"`
	Multiple bool    `json:"multiple,string"`
	Negative float64 `json:"negative,string"`
	Positive float64 `json:"positive,string"`
	Text     string
	Name     string
	Tags     []option `json:"question.tag"`
	Options  []option `json:"question.option"`
	Correct  []option `json:"question.correct"`
}

type questions []cq

func (q questions) Len() int {
	return len(q)
}

func (q questions) Less(i, j int) bool {
	return q[i].Asked.Before(q[j].Asked)
}

func (q questions) Swap(i, j int) {
	q[i], q[j] = q[j], q[i]
}

type cq struct {
	Answer   string    `json:"candidate.answer"`
	Score    float64   `json:"candidate.score,string"`
	Asked    time.Time `json:"question.asked,string"`
	Answered time.Time `json:"question.answered,string"`
	Question []que     `json:"question.uid"`
}

type quiz struct {
	Id       string `json:"_uid_"`
	Duration int    `json:"duration,string"`
	Name     string `json:"name"`
}

type candidates struct {
	Id       string `json:"_uid_"`
	Name     string
	Email    string
	Country  string
	Feedback string
	// Used to calculate percentile, repesents number of candidates having score
	// <= current candidate.
	Idx         int
	Score       float64 `json:",string"`
	CandidateQn []cq    `json:"candidate.question"`
	Complete    bool    `json:"complete,string"`
	Quiz        []quiz  `json:"candidate.quiz"`
	TotalScore  float64
}

type report struct {
	Candidates []candidates `json:"candidate"`
}

func reportQuery(id string) string {
	return `query {
                candidate(_uid_:` + id + `) {
                        _uid_
                        name
                        email
                        country
                        feedback
                        score
                        complete
                        candidate.quiz {
                                _uid_
                                name
                                duration
                        }
                        candidate.question {
                                question.uid {
                                        _uid_
                                        text
                                        name
                                        positive
                                        negative
                                        question.tag {
                                                _uid_
                                                name
                                        }
                                        question.option {
                                                _uid_
                                                name
                                        }
                                        question.correct {
                                                _uid_
                                        }
                                        multiple
                                }
                                question.asked
                                question.answered
                                candidate.answer
                                candidate.score
                        }
                }
        }`
}

type question struct {
	Name      string   `json:"name"`
	Multiple  bool     `json:"multiple"`
	Text      string   `json:"text"`
	TimeTaken string   `json:"time_taken"`
	Positive  float64  `json:"positive"`
	Score     float64  `json:"score"`
	Options   []option `json:"options"`
	Correct   []string `json:"correct"`
	Answers   []string `json:"answers"`
	Answered  bool
	Tags      []string `json:"tags"`
}

type Summary struct {
	Id         string
	Name       string  `json:"name"`
	Email      string  `json:"email"`
	Percentile float64 `json:"percentile"`
	Country    string  `json:"country"`
	QuizName   string
	Feedback   string     `json:"feedback"`
	TimeTaken  string     `json:"time_taken"`
	TotalScore float64    `json:"total_score"`
	MaxScore   float64    `json:"max_score"`
	Questions  []question `json:"questions"`
	Ip         string
}

func uids(opts []option) []string {
	var ids []string
	for _, opt := range opts {
		ids = append(ids, opt.Id)
	}
	return ids
}

func names(opts []option) []string {
	var n []string
	for _, opt := range opts {
		n = append(n, opt.Name)
	}
	return n
}

type ReportError struct {
	Err  string
	Msg  string
	code int
}

type quizRes struct {
	Quiz []struct {
		Candidates []candidates `json:"quiz.candidate"`
	} `json:"quiz"`
}

// ByScore implements sort.Interface for []candidates based on
// the Score field.
type ByScore []candidates

func (a ByScore) Len() int           { return len(a) }
func (a ByScore) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByScore) Less(i, j int) bool { return a[i].Score > a[j].Score }

func percentile(quizId string, cid string) (float64, error) {
	q := `{
	quiz(_uid_: ` + quizId + `) {
		quiz.candidate {
			_uid_
			complete
			score
		}
	}
}
`
	var res quizRes
	if err := dgraph.QueryAndUnmarshal(q, &res); err != nil {
		return 0.0, err
	}

	candidates := res.Quiz[0].Candidates
	i := 0
	for _, cand := range candidates {
		if cand.Complete == true {
			// Lets retain only the candidates who have completed the test
			// for percentile calculation.
			candidates[i] = cand
			i++
		}
	}
	candidates = candidates[:i]

	sort.Sort(ByScore(candidates))
	lastScore := 0.0
	lastIdx := len(candidates)
	for idx, cand := range candidates {
		if cand.Score != lastScore {
			cand.Idx = len(candidates) - idx
			lastIdx = len(candidates) - idx
			lastScore = cand.Score
		} else {
			cand.Idx = lastIdx
		}
		if cand.Id == cid {
			return float64(cand.Idx) / float64(len(candidates)) * 100, nil
		}
	}
	return 0.0, nil
}

func ReportSummary(cid string) (Summary, ReportError) {
	s := Summary{}
	s.Ip = *mail.Ip
	q := reportQuery(cid)
	var rep report
	if err := dgraph.QueryAndUnmarshal(q, &rep); err != nil {
		return s, ReportError{err.Error(), "", http.StatusInternalServerError}
	}

	if len(rep.Candidates) != 1 || rep.Candidates[0].Id == "" || len(rep.Candidates[0].Quiz) != 1 {
		return s, ReportError{"", "Candidate not found.", http.StatusBadRequest}
	}
	s.Id = rep.Candidates[0].Id

	c := rep.Candidates[0]
	s.Name = c.Name
	s.Email = c.Email
	s.Country = c.Country
	s.Feedback = c.Feedback
	// TODO - Check how to obtain sorted results from Dgraph.
	if len(c.CandidateQn) == 0 {
		return s, ReportError{"", "Candidate hasn't started the test", http.StatusBadRequest}
	}

	sort.Sort(questions(c.CandidateQn))
	if !c.CandidateQn[len(c.CandidateQn)-1].Answered.IsZero() {
		s.TimeTaken = c.CandidateQn[len(c.CandidateQn)-1].Answered.Sub(
			c.CandidateQn[0].Asked).String()
	} else {
		// Incase we didn't record the answered for the last qn, say his
		// browser crashed or he didn't finish answering it.
		dur := c.Quiz[0].Duration
		s.TimeTaken = fmt.Sprintf("%vm", dur)
	}
	s.QuizName = c.Quiz[0].Name
	perc, err := percentile(c.Quiz[0].Id, c.Id)
	if err != nil {
		return s, ReportError{"", "Error while calculating percentile", http.StatusInternalServerError}
	}
	s.Percentile = x.ToFixed(perc, 2)

	for _, qn := range c.CandidateQn {
		s.TotalScore += qn.Score
		q := qn.Question[0]
		s.MaxScore += float64(len(q.Correct)) * q.Positive
		answers := strings.Split(qn.Answer, ",")
		sq := question{
			Name:     q.Name,
			Text:     q.Text,
			Options:  q.Options,
			Score:    qn.Score,
			Positive: q.Positive,
			Multiple: q.Multiple,
			Correct:  uids(q.Correct),
			Tags:     names(q.Tags),
			Answers:  answers,
			Answered: len(answers) > 0 && answers[0] != "skip",
		}
		if qn.Answered.IsZero() {
			sq.TimeTaken = "-1"
		} else {
			sq.TimeTaken = qn.Answered.Sub(qn.Asked).String()
		}
		s.Questions = append(s.Questions, sq)
	}
	s.TotalScore = x.ToFixed(s.TotalScore, 2)
	s.MaxScore = x.ToFixed(s.MaxScore, 2)
	return s, ReportError{}
}

func Report(w http.ResponseWriter, r *http.Request) {
	sr := server.Response{}
	vars := mux.Vars(r)
	cid := vars["id"]
	s, re := ReportSummary(cid)
	if re.Msg != "" || re.Err != "" {
		sr.Write(w, re.Err, re.Msg, re.code)
		return
	}

	b, err := json.Marshal(s)
	if err != nil {
		sr.Write(w, err.Error(), "", http.StatusInternalServerError)
		return
	}
	w.Write(b)
}
<|endoftext|>


<commit_before>package oidc

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/ericchiang/oidc/internal"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
)

var (
	// ErrTokenExpired indicates that a token parsed by a verifier has expired.
	ErrTokenExpired = errors.New("ID Token expired")
	// ErrNotSupported indicates that the requested optional OpenID Connect endpoint is not supported by the provider.
	ErrNotSupported = errors.New("endpoint not supported")
)

// InsecureAllowHTTP is the context key to use with golang.org/x/net/context's
// WithValue function to allow issuers to use the scheme "http" instead of "https".
//
//     ctx := context.WithValue(context.TODO(), oidc.InscureAllowHTTP, true)
//     provider, err := oidc.NewProvider(ctx, "http://example.com")
//
var InsecureAllowHTTP internal.ContextKey

// ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
const ScopeOpenID = "openid"

// Provider contains the subset of the OpenID Connect provider metadata needed to request
// and verify ID Tokens.
type Provider struct {
	Issuer      string `json:"issuer"`
	AuthURL     string `json:"authorization_endpoint"`
	TokenURL    string `json:"token_endpoint"`
	JWKSURL     string `json:"jwks_uri"`
	UserInfoURL string `json:"userinfo_endpoint"`

	// Optionally contains extra claims.
	raw map[string]interface{}
}

// NewProvider uses the OpenID Connect disovery mechanism to construct a Provider.
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
	if !allowHTTP(ctx) {
		u, err := url.Parse(issuer)
		if err != nil {
			return nil, fmt.Errorf("oidc: failed to parse issuer as URL: %v", err)
		}
		if u.Scheme != "https" {
			return nil, errors.New("oidc: issuer must have scheme 'https'")
		}
	}

	wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
	resp, err := contextClient(ctx).Get(wellKnown)
	if err != nil {
		return nil, err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("%s: %s", resp.Status, body)
	}
	defer resp.Body.Close()
	var p Provider
	if err := json.Unmarshal(body, &p); err != nil {
		return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
	}
	// raw claims do not get error checks
	json.Unmarshal(body, &p.raw)
	if p.Issuer != issuer {
		return nil, fmt.Errorf("oidc: issuer did not match the issuer field returned by provider metadata")
	}
	return &p, nil
}

// Extra returns additional fields returned by the server during discovery.
func (p *Provider) Extra(key string) interface{} {
	if p.raw != nil {
		return p.raw[key]
	}
	return nil
}

// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
func (p *Provider) Endpoint() oauth2.Endpoint {
	return oauth2.Endpoint{AuthURL: p.AuthURL, TokenURL: p.TokenURL}
}

// UserInfo represents the OpenID Connect userinfo claims.
type UserInfo struct {
	Subject       string `json:"sub"`
	Profile       string `json:"profile"`
	Email         string `json:"email"`
	EmailVerified bool   `json:"email_verified"`

	// Optionally contains extra claims.
	raw map[string]interface{}
}

// Extra returns additional claims returned by the server.
func (u *UserInfo) Extra(key string) interface{} {
	if u.raw != nil {
		return u.raw[key]
	}
	return nil
}

// UserInfo uses the token source to query the provider's user info endpoint.
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
	if p.UserInfoURL == "" {
		return nil, ErrNotSupported
	}
	cli := oauth2.NewClient(ctx, tokenSource)
	resp, err := cli.Get(p.UserInfoURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("%s: %s", resp.Status, body)
	}

	var userInfo UserInfo
	if err := json.Unmarshal(body, &userInfo); err != nil {
		return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err)
	}
	// raw claims do not get error checks
	json.Unmarshal(body, &userInfo.raw)
	return &userInfo, nil
}

// IDTokenVerifier provides verification for ID Tokens.
type IDTokenVerifier struct {
	issuer  string
	keySet  *remoteKeySet
	options []VerificationOption
}

// Verify parse the raw ID Token, verifies it's been signed by the provider, preforms
// additional verification, such as checking the expiration, and returns the claims.
func (v *IDTokenVerifier) Verify(rawIDToken string) (payload []byte, err error) {
	payload, err = v.keySet.verifyJWT(rawIDToken)
	if err != nil {
		return nil, err
	}
	var token struct {
		Exp    float64 `json:"exp"` // JSON numbers are always float64s.
		Issuer string  `json:"iss"`
	}
	if err := json.Unmarshal(payload, &token); err != nil {
		return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
	}
	if v.issuer != token.Issuer {
		return nil, fmt.Errorf("oidc: iss field did not match provider issuer")
	}
	if time.Unix(int64(token.Exp), 0).Before(time.Now().Round(time.Second)) {
		return nil, ErrTokenExpired
	}
	for _, option := range v.options {
		if err := option.verifyIDTokenPayload(payload); err != nil {
			return nil, err
		}
	}
	return payload, nil
}

// NewVerifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
//
// The verifier queries the provider to update keys when a signature cannot be verified by the
// set of keys cached from the previous request.
func (p *Provider) NewVerifier(ctx context.Context, options ...VerificationOption) *IDTokenVerifier {
	return &IDTokenVerifier{
		issuer:  p.Issuer,
		keySet:  newRemoteKeySet(ctx, p.JWKSURL),
		options: options,
	}
}

// VerificationOption is an option provided to Provider.NewVerifier.
type VerificationOption interface {
	verifyIDTokenPayload(raw []byte) error
}

// VerifyAudience ensures that an ID Token was issued for the specific client.
//
// Note that a verified token may be valid for other clients, as OpenID Connect allows a token to have
// multiple audiences.
func VerifyAudience(clientID string) VerificationOption {
	return clientVerifier{clientID}
}

type clientVerifier struct {
	clientID string
}

func (c clientVerifier) verifyIDTokenPayload(payload []byte) error {
	var token struct {
		Aud string `json:"aud"`
	}
	if err := json.Unmarshal(payload, &token); err == nil {
		if token.Aud != c.clientID {
			return errors.New("oidc: id token aud field did not match client_id")
		}
		return nil
	}

	// Aud can optionally be an array of strings
	var token2 struct {
		Aud []string `json:"aud"`
	}
	if err := json.Unmarshal(payload, &token2); err != nil {
		return fmt.Errorf("oidc: failed to unmarshal aud claim: %v", err)
	}
	for _, aud := range token2.Aud {
		if aud == c.clientID {
			return nil
		}
	}
	return errors.New("oidc: id token aud field did not match client_id")
}

// This method is internal to golang.org/x/oauth2. Just copy it.
func contextClient(ctx context.Context) *http.Client {
	if ctx != nil {
		if hc, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
			return hc
		}
	}
	return http.DefaultClient
}

func allowHTTP(ctx context.Context) bool {
	if ctx == nil {
		return false
	}
	allow, ok := ctx.Value(InsecureAllowHTTP).(bool)
	return ok && allow
}
<commit_msg>Add ScopeOfflineAccess<commit_after>package oidc

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/ericchiang/oidc/internal"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
)

var (
	// ErrTokenExpired indicates that a token parsed by a verifier has expired.
	ErrTokenExpired = errors.New("ID Token expired")
	// ErrNotSupported indicates that the requested optional OpenID Connect endpoint is not supported by the provider.
	ErrNotSupported = errors.New("endpoint not supported")
)

// InsecureAllowHTTP is the context key to use with golang.org/x/net/context's
// WithValue function to allow issuers to use the scheme "http" instead of "https".
//
//     ctx := context.WithValue(context.TODO(), oidc.InscureAllowHTTP, true)
//     provider, err := oidc.NewProvider(ctx, "http://example.com")
//
var InsecureAllowHTTP internal.ContextKey

const (
	// ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
	ScopeOpenID = "openid"

	// ScopeOfflineAccess is an optional scope defined by OpenID Connect for requesting
	// OAuth2 refresh tokens.
	//
	// Support for this scope differs between OpenID Connect providers. For instance
	// Google rejects it, favoring appending "access_type=offline" as part of the
	// authorization request instead.
	//
	// See: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
	ScopeOfflineAccess = "offline_access"
)

// Provider contains the subset of the OpenID Connect provider metadata needed to request
// and verify ID Tokens.
type Provider struct {
	Issuer      string `json:"issuer"`
	AuthURL     string `json:"authorization_endpoint"`
	TokenURL    string `json:"token_endpoint"`
	JWKSURL     string `json:"jwks_uri"`
	UserInfoURL string `json:"userinfo_endpoint"`

	// Optionally contains extra claims.
	raw map[string]interface{}
}

// NewProvider uses the OpenID Connect disovery mechanism to construct a Provider.
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
	if !allowHTTP(ctx) {
		u, err := url.Parse(issuer)
		if err != nil {
			return nil, fmt.Errorf("oidc: failed to parse issuer as URL: %v", err)
		}
		if u.Scheme != "https" {
			return nil, errors.New("oidc: issuer must have scheme 'https'")
		}
	}

	wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
	resp, err := contextClient(ctx).Get(wellKnown)
	if err != nil {
		return nil, err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("%s: %s", resp.Status, body)
	}
	defer resp.Body.Close()
	var p Provider
	if err := json.Unmarshal(body, &p); err != nil {
		return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
	}
	// raw claims do not get error checks
	json.Unmarshal(body, &p.raw)
	if p.Issuer != issuer {
		return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
	}
	return &p, nil
}

// Extra returns additional fields returned by the server during discovery.
func (p *Provider) Extra(key string) interface{} {
	if p.raw != nil {
		return p.raw[key]
	}
	return nil
}

// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
func (p *Provider) Endpoint() oauth2.Endpoint {
	return oauth2.Endpoint{AuthURL: p.AuthURL, TokenURL: p.TokenURL}
}

// UserInfo represents the OpenID Connect userinfo claims.
type UserInfo struct {
	Subject       string `json:"sub"`
	Profile       string `json:"profile"`
	Email         string `json:"email"`
	EmailVerified bool   `json:"email_verified"`

	// Optionally contains extra claims.
	raw map[string]interface{}
}

// Extra returns additional claims returned by the server.
func (u *UserInfo) Extra(key string) interface{} {
	if u.raw != nil {
		return u.raw[key]
	}
	return nil
}

// UserInfo uses the token source to query the provider's user info endpoint.
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
	if p.UserInfoURL == "" {
		return nil, ErrNotSupported
	}
	cli := oauth2.NewClient(ctx, tokenSource)
	resp, err := cli.Get(p.UserInfoURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("%s: %s", resp.Status, body)
	}

	var userInfo UserInfo
	if err := json.Unmarshal(body, &userInfo); err != nil {
		return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err)
	}
	// raw claims do not get error checks
	json.Unmarshal(body, &userInfo.raw)
	return &userInfo, nil
}

// IDTokenVerifier provides verification for ID Tokens.
type IDTokenVerifier struct {
	issuer  string
	keySet  *remoteKeySet
	options []VerificationOption
}

// Verify parse the raw ID Token, verifies it's been signed by the provider, preforms
// additional verification, such as checking the expiration, and returns the claims.
func (v *IDTokenVerifier) Verify(rawIDToken string) (payload []byte, err error) {
	payload, err = v.keySet.verifyJWT(rawIDToken)
	if err != nil {
		return nil, err
	}
	var token struct {
		Exp    float64 `json:"exp"` // JSON numbers are always float64s.
		Issuer string  `json:"iss"`
	}
	if err := json.Unmarshal(payload, &token); err != nil {
		return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
	}
	if v.issuer != token.Issuer {
		return nil, fmt.Errorf("oidc: iss field did not match provider issuer")
	}
	if time.Unix(int64(token.Exp), 0).Before(time.Now().Round(time.Second)) {
		return nil, ErrTokenExpired
	}
	for _, option := range v.options {
		if err := option.verifyIDTokenPayload(payload); err != nil {
			return nil, err
		}
	}
	return payload, nil
}

// NewVerifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
//
// The verifier queries the provider to update keys when a signature cannot be verified by the
// set of keys cached from the previous request.
func (p *Provider) NewVerifier(ctx context.Context, options ...VerificationOption) *IDTokenVerifier {
	return &IDTokenVerifier{
		issuer:  p.Issuer,
		keySet:  newRemoteKeySet(ctx, p.JWKSURL),
		options: options,
	}
}

// VerificationOption is an option provided to Provider.NewVerifier.
type VerificationOption interface {
	verifyIDTokenPayload(raw []byte) error
}

// VerifyAudience ensures that an ID Token was issued for the specific client.
//
// Note that a verified token may be valid for other clients, as OpenID Connect allows a token to have
// multiple audiences.
func VerifyAudience(clientID string) VerificationOption {
	return clientVerifier{clientID}
}

type clientVerifier struct {
	clientID string
}

func (c clientVerifier) verifyIDTokenPayload(payload []byte) error {
	var token struct {
		Aud string `json:"aud"`
	}
	if err := json.Unmarshal(payload, &token); err == nil {
		if token.Aud != c.clientID {
			return errors.New("oidc: id token aud field did not match client_id")
		}
		return nil
	}

	// Aud can optionally be an array of strings
	var token2 struct {
		Aud []string `json:"aud"`
	}
	if err := json.Unmarshal(payload, &token2); err != nil {
		return fmt.Errorf("oidc: failed to unmarshal aud claim: %v", err)
	}
	for _, aud := range token2.Aud {
		if aud == c.clientID {
			return nil
		}
	}
	return errors.New("oidc: id token aud field did not match client_id")
}

// This method is internal to golang.org/x/oauth2. Just copy it.
func contextClient(ctx context.Context) *http.Client {
	if ctx != nil {
		if hc, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
			return hc
		}
	}
	return http.DefaultClient
}

func allowHTTP(ctx context.Context) bool {
	if ctx == nil {
		return false
	}
	allow, ok := ctx.Value(InsecureAllowHTTP).(bool)
	return ok && allow
}
<|endoftext|>


<commit_before>/*
An API client for opensubtitles.org

This is a client for the OSDb protocol. Currently the package only allows movie
identification, and subtitles search.
*/
package osdb

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"os"
	"strings"

	"github.com/kolo/xmlrpc"
)

const (
	ChunkSize     = 65536 // 64k
	Server        = "http://api.opensubtitles.org/xml-rpc"
	SearchLimit   = 100
	SuccessStatus = "200 OK"
)

var (
	UserAgent = "OS Test User Agent" // OSDB's test agent
	Token     = ""
	Client, _ = xmlrpc.NewClient(Server, nil)
)

type Subtitle struct {
	IDMovie            string `xmlrpc:"IDMovie"`
	IDMovieImdb        string `xmlrpc:"IDMovieImdb"`
	IDSubMovieFile     string `xmlrpc:"IDSubMovieFile"`
	IDSubtitle         string `xmlrpc:"IDSubtitle"`
	IDSubtitleFile     string `xmlrpc:"IDSubtitleFile"`
	ISO639             string `xmlrpc:"ISO639"`
	LanguageName       string `xmlrpc:"LanguageName"`
	MatchedBy          string `xmlrpc:"MatchedBy"`
	MovieByteSize      string `xmlrpc:"MovieByteSize"`
	MovieFPS           string `xmlrpc:"MovieFPS"`
	MovieHash          string `xmlrpc:"MovieHash"`
	MovieImdbRating    string `xmlrpc:"MovieImdbRating"`
	MovieKind          string `xmlrpc:"MovieKind"`
	MovieName          string `xmlrpc:"MovieName"`
	MovieNameEng       string `xmlrpc:"MovieNameEng"`
	MovieReleaseName   string `xmlrpc:"MovieReleaseName"`
	MovieTimeMS        string `xmlrpc:"MovieTimeMS"`
	MovieYear          string `xmlrpc:"MovieYear"`
	QueryNumber        string `xmlrpc:"QueryNumber"`
	SeriesEpisode      string `xmlrpc:"SeriesEpisode"`
	SeriesIMDBParent   string `xmlrpc:"SeriesIMDBParent"`
	SeriesSeason       string `xmlrpc:"SeriesSeason"`
	SubActualCD        string `xmlrpc:"SubActualCD"`
	SubAddDate         string `xmlrpc:"SubAddDate"`
	SubAuthorComment   string `xmlrpc:"SubAuthorComment"`
	SubBad             string `xmlrpc:"SubBad"`
	SubComments        string `xmlrpc:"SubComments"`
	SubDownloadLink    string `xmlrpc:"SubDownloadLink"`
	SubDownloadsCnt    string `xmlrpc:"SubDownloadsCnt"`
	SubFeatured        string `xmlrpc:"SubFeatured"`
	SubFileName        string `xmlrpc:"SubFileName"`
	SubFormat          string `xmlrpc:"SubFormat"`
	SubHash            string `xmlrpc:"SubHash"`
	SubHD              string `xmlrpc:"SubHD"`
	SubHearingImpaired string `xmlrpc:"SubHearingImpaired"`
	SubLanguageID      string `xmlrpc:"SubLanguageID"`
	SubRating          string `xmlrpc:"SubRating"`
	SubSize            string `xmlrpc:"SubSize"`
	SubSumCD           string `xmlrpc:"SubSumCD"`
	SubtitlesLink      string `xmlrpc:"SubtitlesLink"`
	UserID             string `xmlrpc:"UserID"`
	UserNickName       string `xmlrpc:"UserNickName"`
	UserRank           string `xmlrpc:"UserRank"`
	ZipDownloadLink    string `xmlrpc:"ZipDownloadLink"`
}

// Generate a OSDB hash for a file
func Hash(path string) (hash uint64, err error) {
	// Check file size.
	fi, err := os.Stat(path)
	if err != nil {
		return
	}
	if fi.Size() < ChunkSize {
		return 0, fmt.Errorf("File is too small")
	}

	file, err := os.Open(path)
	if err != nil {
		return
	}

	// Read head and tail blocks.
	buf := make([]byte, ChunkSize*2)
	err = readChunk(file, 0, buf[:ChunkSize])
	if err != nil {
		return
	}
	err = readChunk(file, fi.Size()-ChunkSize, buf[ChunkSize:])
	if err != nil {
		return
	}

	// Convert to uint64, and sum.
	var nums [(ChunkSize * 2) / 8]uint64
	reader := bytes.NewReader(buf)
	err = binary.Read(reader, binary.LittleEndian, &nums)
	if err != nil {
		return 0, err
	}
	for _, num := range nums {
		hash += num
	}

	return hash + uint64(fi.Size()), nil
}

// Search subtitles in `languages` for a file at `path`.
func FileSearch(path string, langs []string) ([]Subtitle, error) {
	if err := ensureToken(); err != nil {
		return nil, err
	}

	// Query OSDB....
	params, err := hashSearchParams(path, langs)
	if err != nil {
		return nil, err
	}
	res := struct {
		Data []Subtitle `xmlrpc:"data"`
	}{}
	if err = Client.Call("SearchSubtitles", params, &res); err != nil {
		return nil, err
	}
	return res.Data, nil
}

// Build query parameters for hash-based movie search.
func hashSearchParams(path string, langs []string) (interface{}, error) {
	// File size
	fi, err := os.Stat(path)
	if err != nil {
		return nil, err
	}
	size := fi.Size()

	// File hash
	h, err := Hash(path)
	if err != nil {
		return nil, err
	}
	return []interface{}{
		Token,
		[]struct {
			Hash  string `xmlrpc:"moviehash"`
			Size  int64  `xmlrpc:"moviebytesize"`
			Langs string `xmlrpc:"sublanguageid"`
		}{{
			fmt.Sprintf("%x", h),
			size,
			strings.Join(langs, ","),
		}},
	}, nil
}

// Login to the API, and return a session token.
func Login(user string, pass string, lang string) (tok string, err error) {
	args := []interface{}{user, pass, lang, UserAgent}
	res := struct {
		Status string `xmlrpc:"status"`
		Token  string `xmlrpc:"token"`
	}{}
	if err = Client.Call("LogIn", args, &res); err != nil {
		return
	}

	if res.Status != SuccessStatus {
		return tok, fmt.Errorf("login error: %s", res.Status)
	}
	tok = res.Token
	return
}

// Logout
func Logout(tok string) (err error) {
	args := []interface{}{tok}
	res := struct {
		Status string `xmlrpc:"status"`
	}{}
	return Client.Call("LogOut", args, &res)
}

// Read a chunk of a file at `offset` so as to fill `buf`.
func readChunk(file *os.File, offset int64, buf []byte) (err error) {
	n, err := file.ReadAt(buf, offset)
	if err != nil {
		return
	}
	if n != ChunkSize {
		return fmt.Errorf("Invalid read", n)
	}
	return
}

// Login anonymously if no Token has been set yet.
func ensureToken() (err error) {
	if Token != "" {
		return
	}
	tok, err := Login("", "", "")
	Token = tok
	return
}
<commit_msg>Change hashSearchParams' return type<commit_after>/*
An API client for opensubtitles.org

This is a client for the OSDb protocol. Currently the package only allows movie
identification, and subtitles search.
*/
package osdb

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"os"
	"strings"

	"github.com/kolo/xmlrpc"
)

const (
	ChunkSize     = 65536 // 64k
	Server        = "http://api.opensubtitles.org/xml-rpc"
	SearchLimit   = 100
	SuccessStatus = "200 OK"
)

var (
	UserAgent = "OS Test User Agent" // OSDB's test agent
	Token     = ""
	Client, _ = xmlrpc.NewClient(Server, nil)
)

type Subtitle struct {
	IDMovie            string `xmlrpc:"IDMovie"`
	IDMovieImdb        string `xmlrpc:"IDMovieImdb"`
	IDSubMovieFile     string `xmlrpc:"IDSubMovieFile"`
	IDSubtitle         string `xmlrpc:"IDSubtitle"`
	IDSubtitleFile     string `xmlrpc:"IDSubtitleFile"`
	ISO639             string `xmlrpc:"ISO639"`
	LanguageName       string `xmlrpc:"LanguageName"`
	MatchedBy          string `xmlrpc:"MatchedBy"`
	MovieByteSize      string `xmlrpc:"MovieByteSize"`
	MovieFPS           string `xmlrpc:"MovieFPS"`
	MovieHash          string `xmlrpc:"MovieHash"`
	MovieImdbRating    string `xmlrpc:"MovieImdbRating"`
	MovieKind          string `xmlrpc:"MovieKind"`
	MovieName          string `xmlrpc:"MovieName"`
	MovieNameEng       string `xmlrpc:"MovieNameEng"`
	MovieReleaseName   string `xmlrpc:"MovieReleaseName"`
	MovieTimeMS        string `xmlrpc:"MovieTimeMS"`
	MovieYear          string `xmlrpc:"MovieYear"`
	QueryNumber        string `xmlrpc:"QueryNumber"`
	SeriesEpisode      string `xmlrpc:"SeriesEpisode"`
	SeriesIMDBParent   string `xmlrpc:"SeriesIMDBParent"`
	SeriesSeason       string `xmlrpc:"SeriesSeason"`
	SubActualCD        string `xmlrpc:"SubActualCD"`
	SubAddDate         string `xmlrpc:"SubAddDate"`
	SubAuthorComment   string `xmlrpc:"SubAuthorComment"`
	SubBad             string `xmlrpc:"SubBad"`
	SubComments        string `xmlrpc:"SubComments"`
	SubDownloadLink    string `xmlrpc:"SubDownloadLink"`
	SubDownloadsCnt    string `xmlrpc:"SubDownloadsCnt"`
	SubFeatured        string `xmlrpc:"SubFeatured"`
	SubFileName        string `xmlrpc:"SubFileName"`
	SubFormat          string `xmlrpc:"SubFormat"`
	SubHash            string `xmlrpc:"SubHash"`
	SubHD              string `xmlrpc:"SubHD"`
	SubHearingImpaired string `xmlrpc:"SubHearingImpaired"`
	SubLanguageID      string `xmlrpc:"SubLanguageID"`
	SubRating          string `xmlrpc:"SubRating"`
	SubSize            string `xmlrpc:"SubSize"`
	SubSumCD           string `xmlrpc:"SubSumCD"`
	SubtitlesLink      string `xmlrpc:"SubtitlesLink"`
	UserID             string `xmlrpc:"UserID"`
	UserNickName       string `xmlrpc:"UserNickName"`
	UserRank           string `xmlrpc:"UserRank"`
	ZipDownloadLink    string `xmlrpc:"ZipDownloadLink"`
}

// Generate a OSDB hash for a file
func Hash(path string) (hash uint64, err error) {
	// Check file size.
	fi, err := os.Stat(path)
	if err != nil {
		return
	}
	if fi.Size() < ChunkSize {
		return 0, fmt.Errorf("File is too small")
	}

	file, err := os.Open(path)
	if err != nil {
		return
	}

	// Read head and tail blocks.
	buf := make([]byte, ChunkSize*2)
	err = readChunk(file, 0, buf[:ChunkSize])
	if err != nil {
		return
	}
	err = readChunk(file, fi.Size()-ChunkSize, buf[ChunkSize:])
	if err != nil {
		return
	}

	// Convert to uint64, and sum.
	var nums [(ChunkSize * 2) / 8]uint64
	reader := bytes.NewReader(buf)
	err = binary.Read(reader, binary.LittleEndian, &nums)
	if err != nil {
		return 0, err
	}
	for _, num := range nums {
		hash += num
	}

	return hash + uint64(fi.Size()), nil
}

// Search subtitles in `languages` for a file at `path`.
func FileSearch(path string, langs []string) ([]Subtitle, error) {
	if err := ensureToken(); err != nil {
		return nil, err
	}

	// Query OSDB....
	params, err := hashSearchParams(path, langs)
	if err != nil {
		return nil, err
	}
	res := struct {
		Data []Subtitle `xmlrpc:"data"`
	}{}
	if err = Client.Call("SearchSubtitles", *params, &res); err != nil {
		return nil, err
	}
	return res.Data, nil
}

// Build query parameters for hash-based movie search.
func hashSearchParams(path string, langs []string) (*[]interface{}, error) {
	// File size
	fi, err := os.Stat(path)
	if err != nil {
		return nil, err
	}
	size := fi.Size()

	// File hash
	h, err := Hash(path)
	if err != nil {
		return nil, err
	}
	params := []interface{}{
		Token,
		[]struct {
			Hash  string `xmlrpc:"moviehash"`
			Size  int64  `xmlrpc:"moviebytesize"`
			Langs string `xmlrpc:"sublanguageid"`
		}{{
			fmt.Sprintf("%x", h),
			size,
			strings.Join(langs, ","),
		}},
	}
	return &params, nil
}

// Login to the API, and return a session token.
func Login(user string, pass string, lang string) (tok string, err error) {
	args := []interface{}{user, pass, lang, UserAgent}
	res := struct {
		Status string `xmlrpc:"status"`
		Token  string `xmlrpc:"token"`
	}{}
	if err = Client.Call("LogIn", args, &res); err != nil {
		return
	}

	if res.Status != SuccessStatus {
		return tok, fmt.Errorf("login error: %s", res.Status)
	}
	tok = res.Token
	return
}

// Logout
func Logout(tok string) (err error) {
	args := []interface{}{tok}
	res := struct {
		Status string `xmlrpc:"status"`
	}{}
	return Client.Call("LogOut", args, &res)
}

// Read a chunk of a file at `offset` so as to fill `buf`.
func readChunk(file *os.File, offset int64, buf []byte) (err error) {
	n, err := file.ReadAt(buf, offset)
	if err != nil {
		return
	}
	if n != ChunkSize {
		return fmt.Errorf("Invalid read", n)
	}
	return
}

// Login anonymously if no Token has been set yet.
func ensureToken() (err error) {
	if Token != "" {
		return
	}
	tok, err := Login("", "", "")
	Token = tok
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package client

import (
	"crypto/tls"
	"errors"
	"fmt"
	"regexp"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/openconfig/gnmi/path"
	gpb "github.com/openconfig/gnmi/proto/gnmi"
)

// NotificationHandler is a type for the client specific handler function.
//
// Client implementations will pass all kinds of received notifications as they
// arrive.
type NotificationHandler func(Notification) error

// ProtoHandler is a type for the raw handling of the RPC layer. Most users
// should use NotificationHandler instead.
type ProtoHandler func(proto.Message) error

// Type defines the type of query in a Query.
type Type int

// NewType returns a new QueryType based on the provided string.
func NewType(s string) Type {
	v, ok := typeConst[s]
	if !ok {
		return Unknown
	}
	return v
}

// String returns the string representation of the QueryType.
func (q Type) String() string {
	return typeString[q]
}

const (
	// Unknown is an unknown query and should always be treated as an error.
	Unknown Type = iota
	// Once will perform a Once query against the agent.
	Once
	// Poll will perform a Polling query against the agent.
	Poll
	// Stream will perform a Streaming query against the agent.
	Stream
)

var (
	typeString = map[Type]string{
		Unknown: "unknown",
		Once:    "once",
		Poll:    "poll",
		Stream:  "stream",
	}

	typeConst = map[string]Type{
		"unknown": Unknown,
		"once":    Once,
		"poll":    Poll,
		"stream":  Stream,
	}

	// Pre-compiled regex to match ASCII characters between [\x20-\x7E]
	// i.e., printable ASCII characters and space
	// https://github.com/grpc/blob/master/doc/PROTOCOL-HTTP2.md
	printableASCII = regexp.MustCompile(`^[\x20-\x7E]*$`).MatchString
)

// Destination contains data used to connect to a server.
type Destination struct {
	// Addrs is a slice of addresses by which a target may be reached. Most
	// clients will only handle the first element.
	Addrs []string
	// Target is the target of the query.  Maybe empty if the query is performed
	// against an end target vs. a collector.
	Target string
	// Replica is the specific backend to contact.  This field is implementation
	// specific and for direct agent communication should not be set. default is
	// first available.
	Replica int
	// Timeout is the connection timeout for the query. It will *not* prevent a
	// slow (or streaming) query from completing, this only affects the initial
	// connection and broken connection detection.
	//
	// If Timeout is not set, default is 1 minute.
	Timeout time.Duration
	// Credentials are used for authentication with the target. Optional.
	Credentials *Credentials
	// TLS config to use when connecting to target. Optional.
	TLS *tls.Config
	// Extra contains arbitrary additional metadata to be passed to the
	// target. Optional.
	Extra map[string]string
}

// Validate validates the fields of Destination.
func (d Destination) Validate() error {
	if len(d.Addrs) == 0 {
		return errors.New("Destination.Addrs is empty")
	}
	if d.Credentials != nil {
		return d.Credentials.validate()
	}
	return nil
}

// Query contains all of the parameters necessary to initiate the query.
type Query struct {
	// Addrs is a slice of addresses by which a target may be reached. Most
	// clients will only handle the first element.
	Addrs []string
	// Target is the target of the query.  Maybe empty if the query is performed
	// against an end target vs. a collector.
	Target string
	// Replica is the specific backend to contact.  This field is implementation
	// specific and for direct agent communication should not be set. default is
	// first available.
	Replica int
	// UpdatesOnly will only stream incremental updates rather than providing the
	// client with an initial snapshot.  This again is implementation specific
	// if the agent doesn't not accept that query it is up the client library to
	// decide wheter to return an error or to make a normal subscription then
	// ignore the initial sync and only provide increment updates.
	UpdatesOnly bool
	// Queries contains the list of Paths to query.
	Queries []Path
	// Type of query to perform.
	Type Type
	// Timeout is the connection timeout for the query. It will *not* prevent a
	// slow (or streaming) query from completing, this only affects the initial
	// connection and broken connection detection.
	//
	// If Timeout is not set, default is 1 minute.
	Timeout time.Duration
	// NotificationHandler is the per notification callback handed to a vendor
	// specific implementation. For every notificaiton this call back will be
	// called.
	NotificationHandler NotificationHandler
	// ProtoHandler, if set, will receive all response protos sent by the
	// backend. Only one of NotificationHandler or ProtoHandler may be
	// set.
	ProtoHandler ProtoHandler
	// Credentials are used for authentication with the target. Optional.
	Credentials *Credentials
	// TLS config to use when connecting to target. Optional.
	TLS *tls.Config
	// Extra contains arbitrary additional metadata to be passed to the
	// target. Optional.
	Extra map[string]string
	// SubReq is an optional field. If not nil, gnmi client implementation uses
	// it rather than generating from client.Query while sending gnmi Subscribe RPC.
	SubReq *gpb.SubscribeRequest
}

// Destination extracts a Destination instance out of Query fields.
//
// Ideally we would embed Destination in Query. But in order to not break the
// existing API we have this workaround.
func (q Query) Destination() Destination {
	return Destination{
		Addrs:       q.Addrs,
		Target:      q.Target,
		Replica:     q.Replica,
		Timeout:     q.Timeout,
		Credentials: q.Credentials,
		TLS:         q.TLS,
		Extra:       q.Extra,
	}
}

// Credentials contains information necessary to authenticate with the target.
// Currently only username/password are supported, but may contain TLS client
// certificate in the future.
type Credentials struct {
	Username string
	Password string
}

// Validates the credentials against printable ASCII characters
func (c Credentials) validate() error {
	if !printableASCII(c.Username) {
		return errors.New("Credentials.Username contains non printable ASCII characters")
	}
	if !printableASCII(c.Password) {
		return errors.New("Credentials.Password contains non printable ASCII characters")
	}
	return nil
}

// NewQuery returns a populated Query from given gnmi SubscribeRequest.
// Query fields that are not part of SubscribeRequest must be set on
// the returned object.
// During transtion to support only gnmi, having Query and SubscribeRequest
// in sync is important. There are two approaches to ensure that; one is
// validating whether Query and SubscribeRequest are same after they are set, the other is
// populating the fields of Query from SubscribeRequest and filling out the rest
// on the returned object. NewQuery embraces the latter option.
func NewQuery(sr *gpb.SubscribeRequest) (Query, error) {
	q := Query{}
	if sr == nil {
		return q, errors.New("input is nil")
	}

	s, ok := sr.Request.(*gpb.SubscribeRequest_Subscribe)
	if !ok {
		return q, fmt.Errorf("got %T, want gpb.SubscribeRequest_Subscribe as input", sr)
	}

	if s.Subscribe == nil {
		return q, errors.New("Subscribe field in SubscribeRequest_Subscribe is nil")
	}

	if s.Subscribe.Prefix == nil {
		return q, errors.New("Prefix field in SubscriptionList is nil")
	}
	q.Target = s.Subscribe.Prefix.Target
	q.UpdatesOnly = s.Subscribe.UpdatesOnly
	switch s.Subscribe.Mode {
	case gpb.SubscriptionList_ONCE:
		q.Type = Once
	case gpb.SubscriptionList_POLL:
		q.Type = Poll
	case gpb.SubscriptionList_STREAM:
		q.Type = Stream
	}

	for _, su := range s.Subscribe.Subscription {
		q.Queries = append(q.Queries, path.ToStrings(su.Path, false))
	}
	q.SubReq = sr

	return q, nil
}

// Validate validates that query contains valid values that any client should
// be able use to form a valid backend request.
func (q Query) Validate() error {
	if err := q.Destination().Validate(); err != nil {
		return err
	}
	switch {
	case q.Type == Unknown:
		return errors.New("Query type cannot be Unknown")
	case len(q.Queries) == 0:
		return errors.New("Query.Queries not set")
	case q.NotificationHandler != nil && q.ProtoHandler != nil:
		return errors.New("only one of Notification or ProtoHandler must be set")
	case q.NotificationHandler == nil && q.ProtoHandler == nil:
		return errors.New("one of Notification or ProtoHandler must be set")
	}
	return nil
}
<commit_msg>multiple address support, including: (1) pass multiple addresses from backend to proxy (2) proxy handles multiple addresses and uses the first returned connection.<commit_after>/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package client

import (
	"crypto/tls"
	"errors"
	"fmt"
	"regexp"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/openconfig/gnmi/path"
	gpb "github.com/openconfig/gnmi/proto/gnmi"
)

// NotificationHandler is a type for the client specific handler function.
//
// Client implementations will pass all kinds of received notifications as they
// arrive.
type NotificationHandler func(Notification) error

// ProtoHandler is a type for the raw handling of the RPC layer. Most users
// should use NotificationHandler instead.
type ProtoHandler func(proto.Message) error

// Type defines the type of query in a Query.
type Type int

// NewType returns a new QueryType based on the provided string.
func NewType(s string) Type {
	v, ok := typeConst[s]
	if !ok {
		return Unknown
	}
	return v
}

// String returns the string representation of the QueryType.
func (q Type) String() string {
	return typeString[q]
}

const (
	// Unknown is an unknown query and should always be treated as an error.
	Unknown Type = iota
	// Once will perform a Once query against the agent.
	Once
	// Poll will perform a Polling query against the agent.
	Poll
	// Stream will perform a Streaming query against the agent.
	Stream
)

var (
	typeString = map[Type]string{
		Unknown: "unknown",
		Once:    "once",
		Poll:    "poll",
		Stream:  "stream",
	}

	typeConst = map[string]Type{
		"unknown": Unknown,
		"once":    Once,
		"poll":    Poll,
		"stream":  Stream,
	}

	// Pre-compiled regex to match ASCII characters between [\x20-\x7E]
	// i.e., printable ASCII characters and space
	// https://github.com/grpc/blob/master/doc/PROTOCOL-HTTP2.md
	printableASCII = regexp.MustCompile(`^[\x20-\x7E]*$`).MatchString
)

// Destination contains data used to connect to a server.
type Destination struct {
	// Addrs is a slice of addresses by which a target may be reached. Most
	// clients will only handle the first element.
	Addrs []string
	// Target is the target of the query.  Maybe empty if the query is performed
	// against an end target vs. a collector.
	Target string
	// Replica is the specific backend to contact.  This field is implementation
	// specific and for direct agent communication should not be set. default is
	// first available.
	Replica int
	// Timeout is the connection timeout for the query. It will *not* prevent a
	// slow (or streaming) query from completing, this only affects the initial
	// connection and broken connection detection.
	//
	// If Timeout is not set, default is 1 minute.
	Timeout time.Duration
	// Credentials are used for authentication with the target. Optional.
	Credentials *Credentials
	// TLS config to use when connecting to target. Optional.
	TLS *tls.Config
	// Extra contains arbitrary additional metadata to be passed to the
	// target. Optional.
	Extra map[string]string
}

// Validate validates the fields of Destination.
func (d Destination) Validate() error {
	if len(d.Addrs) == 0 {
		return errors.New("Destination.Addrs is empty")
	}
	if d.Credentials != nil {
		return d.Credentials.validate()
	}
	return nil
}

// Query contains all of the parameters necessary to initiate the query.
type Query struct {
	// Addrs is a slice of addresses by which a target may be reached. Most
	// clients will only handle the first element.
	Addrs         []string
	AddressChains [][]string
	// Target is the target of the query.  Maybe empty if the query is performed
	// against an end target vs. a collector.
	Target string
	// Replica is the specific backend to contact.  This field is implementation
	// specific and for direct agent communication should not be set. default is
	// first available.
	Replica int
	// UpdatesOnly will only stream incremental updates rather than providing the
	// client with an initial snapshot.  This again is implementation specific
	// if the agent doesn't not accept that query it is up the client library to
	// decide wheter to return an error or to make a normal subscription then
	// ignore the initial sync and only provide increment updates.
	UpdatesOnly bool
	// Queries contains the list of Paths to query.
	Queries []Path
	// Type of query to perform.
	Type Type
	// Timeout is the connection timeout for the query. It will *not* prevent a
	// slow (or streaming) query from completing, this only affects the initial
	// connection and broken connection detection.
	//
	// If Timeout is not set, default is 1 minute.
	Timeout time.Duration
	// NotificationHandler is the per notification callback handed to a vendor
	// specific implementation. For every notificaiton this call back will be
	// called.
	NotificationHandler NotificationHandler
	// ProtoHandler, if set, will receive all response protos sent by the
	// backend. Only one of NotificationHandler or ProtoHandler may be
	// set.
	ProtoHandler ProtoHandler
	// Credentials are used for authentication with the target. Optional.
	Credentials *Credentials
	// TLS config to use when connecting to target. Optional.
	TLS *tls.Config
	// Extra contains arbitrary additional metadata to be passed to the
	// target. Optional.
	Extra map[string]string
	// SubReq is an optional field. If not nil, gnmi client implementation uses
	// it rather than generating from client.Query while sending gnmi Subscribe RPC.
	SubReq *gpb.SubscribeRequest
}

// Destination extracts a Destination instance out of Query fields.
//
// Ideally we would embed Destination in Query. But in order to not break the
// existing API we have this workaround.
func (q Query) Destination() Destination {
	return Destination{
		Addrs:       q.Addrs,
		Target:      q.Target,
		Replica:     q.Replica,
		Timeout:     q.Timeout,
		Credentials: q.Credentials,
		TLS:         q.TLS,
		Extra:       q.Extra,
	}
}

// Credentials contains information necessary to authenticate with the target.
// Currently only username/password are supported, but may contain TLS client
// certificate in the future.
type Credentials struct {
	Username string
	Password string
}

// Validates the credentials against printable ASCII characters
func (c Credentials) validate() error {
	if !printableASCII(c.Username) {
		return errors.New("Credentials.Username contains non printable ASCII characters")
	}
	if !printableASCII(c.Password) {
		return errors.New("Credentials.Password contains non printable ASCII characters")
	}
	return nil
}

// NewQuery returns a populated Query from given gnmi SubscribeRequest.
// Query fields that are not part of SubscribeRequest must be set on
// the returned object.
// During transtion to support only gnmi, having Query and SubscribeRequest
// in sync is important. There are two approaches to ensure that; one is
// validating whether Query and SubscribeRequest are same after they are set, the other is
// populating the fields of Query from SubscribeRequest and filling out the rest
// on the returned object. NewQuery embraces the latter option.
func NewQuery(sr *gpb.SubscribeRequest) (Query, error) {
	q := Query{}
	if sr == nil {
		return q, errors.New("input is nil")
	}

	s, ok := sr.Request.(*gpb.SubscribeRequest_Subscribe)
	if !ok {
		return q, fmt.Errorf("got %T, want gpb.SubscribeRequest_Subscribe as input", sr)
	}

	if s.Subscribe == nil {
		return q, errors.New("Subscribe field in SubscribeRequest_Subscribe is nil")
	}

	if s.Subscribe.Prefix == nil {
		return q, errors.New("Prefix field in SubscriptionList is nil")
	}
	q.Target = s.Subscribe.Prefix.Target
	q.UpdatesOnly = s.Subscribe.UpdatesOnly
	switch s.Subscribe.Mode {
	case gpb.SubscriptionList_ONCE:
		q.Type = Once
	case gpb.SubscriptionList_POLL:
		q.Type = Poll
	case gpb.SubscriptionList_STREAM:
		q.Type = Stream
	}

	for _, su := range s.Subscribe.Subscription {
		q.Queries = append(q.Queries, path.ToStrings(su.Path, false))
	}
	q.SubReq = sr

	return q, nil
}

// Validate validates that query contains valid values that any client should
// be able use to form a valid backend request.
func (q Query) Validate() error {
	if err := q.Destination().Validate(); err != nil {
		return err
	}
	switch {
	case q.Type == Unknown:
		return errors.New("Query type cannot be Unknown")
	case len(q.Queries) == 0:
		return errors.New("Query.Queries not set")
	case q.NotificationHandler != nil && q.ProtoHandler != nil:
		return errors.New("only one of Notification or ProtoHandler must be set")
	case q.NotificationHandler == nil && q.ProtoHandler == nil:
		return errors.New("one of Notification or ProtoHandler must be set")
	}
	return nil
}
<|endoftext|>


<commit_before>package plex

import (
	"log"
	"os"
	"testing"
)

// Test the failure to create a token file.
func TestInvalidTokenFile(t *testing.T) {
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "zzz:/invalidPath/tokenfile"
	defer func() { tokenFile = oldtokenfile }()

	// Check if the token function returns the value from the test token file.
	err := cacheToken("test")
	if err == nil {
		t.Error("Was able to create invalid token file")
	}
}

// Test reading of the token from a temporary token file.
func TestTokenFileRead(t *testing.T) {
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "testTokenFile"
	defer func() { tokenFile = oldtokenfile }()

	// Create a new temporary token file containing "ValidToken".
	f, err := os.Create(tokenFile)
	if err != nil {
		t.Fatal("Unable to create token file.")
	}
	f.WriteString("ValidToken")

	// Check if the token function returns the value from the test token file.
	token := Token()
	if token != "ValidToken" {
		t.Error("Tokenfile does not contain 'ValidToken'")
	}

	// Cleanup the temporary token file.
	f.Close()
	os.Remove(f.Name())
}

// Test getting a new token with a valid username and password, then writing it to file.
func TestTokenGeneration(t *testing.T) {
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "testTokenFile"
	defer func() { tokenFile = oldtokenfile }()

	err := cacheToken("ValidToken")
	if err != nil {
		t.Error("Unable to generate tokenfile")
	}

	// Check if the token function returns the value from the test token file.
	token := Token()
	if token != "ValidToken" {
		t.Error("Generated token does not contain 'ValidToken'")
	}

	// Cleanup the temporary token file.
	f, err := os.Open(tokenFile)
	if err != nil {
		log.Println(err)
	}
	f.Close()
	if err := os.Remove(f.Name()); err != nil {
		log.Printf("Error removing file: %s", err)
	}
}

// Test removing a new token file.
func TestTokenRemoval(t *testing.T) {
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "testTokenFile"
	defer func() { tokenFile = oldtokenfile }()

	// Create a new temporary token file containing "ValidToken".
	f, err := os.Create(tokenFile)
	if err != nil {
		t.Fatal("Unable to create token file.")
	}
	f.WriteString("ValidToken")
	f.Close()

	if err := RemoveCachedToken(); err != nil {
		t.Error("Error removing token file")
	}
}
<commit_msg>Feature/add tests (#67)<commit_after>package plex

import (
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"testing"
)

// Test the failure to create a token file.
func TestInvalidTokenFile(t *testing.T) {
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "zzz:/invalidPath/tokenfile"
	defer func() { tokenFile = oldtokenfile }()

	// Check if the token function returns the value from the test token file.
	err := cacheToken("test")
	if err == nil {
		t.Error("Was able to create invalid token file")
	}
}

// Test reading of the token from a temporary token file.
func TestTokenFileRead(t *testing.T) {
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "testTokenFile"
	defer func() { tokenFile = oldtokenfile }()

	// Create a new temporary token file containing "ValidToken".
	f, err := os.Create(tokenFile)
	if err != nil {
		t.Fatal("Unable to create token file.")
	}
	f.WriteString("ValidToken")

	// Check if the token function returns the value from the test token file.
	token := Token()
	if token != "ValidToken" {
		t.Error("Tokenfile does not contain 'ValidToken'")
	}

	// Cleanup the temporary token file.
	f.Close()
	os.Remove(f.Name())
}

// Test getting a new token with a valid username and password, then writing it to file.
func TestTokenGeneration(t *testing.T) {
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "testTokenFile"
	defer func() { tokenFile = oldtokenfile }()

	err := cacheToken("ValidToken")
	if err != nil {
		t.Error("Unable to generate tokenfile")
	}

	// Check if the token function returns the value from the test token file.
	token := Token()
	if token != "ValidToken" {
		t.Error("Generated token does not contain 'ValidToken'")
	}

	// Cleanup the temporary token file.
	f, err := os.Open(tokenFile)
	if err != nil {
		log.Println(err)
	}
	f.Close()
	if err := os.Remove(f.Name()); err != nil {
		log.Printf("Error removing file: %s", err)
	}
}

// Test removing a new token file.
func TestTokenRemoval(t *testing.T) {
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "testTokenFile"
	defer func() { tokenFile = oldtokenfile }()

	// Create a new temporary token file containing "ValidToken".
	f, err := os.Create(tokenFile)
	if err != nil {
		t.Fatal("Unable to create token file.")
	}
	f.WriteString("ValidToken")
	f.Close()

	if err := RemoveCachedToken(); err != nil {
		t.Error("Error removing token file")
	}
}

// Test missing token file
func TestNewTokenfile(t *testing.T) {
	want := ""
	// Replace the tokenfile path for the duration of this test.
	oldtokenfile := tokenFile
	tokenFile = "testTokenFile"
	defer func() { tokenFile = oldtokenfile }()

	got := Token()
	if got != want {
		t.Errorf("Token() got %v want %v", got, want)
	}
}

// Test_apiRequest tests the apiRequest function
func Test_apiRequest(t *testing.T) {
	type args struct {
		method string
		url    string
		token  string
		body   io.Reader
	}
	tests := []struct {
		name       string
		args       args
		wantStatus int
		wantBody   string
		wantErr    bool
	}{
		{
			name: "Successful request",
			args: args{
				method: "GET",
				url:    "http://www.mocky.io/v2/5a07ad672f0000ae0ee610f1",
				token:  "testtoken",
				body:   nil,
			},
			wantStatus: http.StatusOK,
			wantBody:   "{ \"hello\": \"world\" }",
			wantErr:    false,
		},
		{
			name: "Bad URL",
			args: args{
				method: "GET",
				url:    "http://badurl",
				token:  "testtoken",
				body:   nil,
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := apiRequest(tt.args.method, tt.args.url, tt.args.token, tt.args.body)
			if (err != nil) != tt.wantErr {
				t.Errorf("apiRequest() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if tt.wantErr != true {
				if got.StatusCode != tt.wantStatus {
					t.Errorf("apiRequest() response status = %v, want %v", got.StatusCode, tt.wantStatus)
					return
				}

				defer got.Body.Close()
				body, err := ioutil.ReadAll(got.Body)
				if err != nil {
					t.Fatal("Error reading response body")
				}
				if string(body) != tt.wantBody {
					t.Errorf("apiRequest() body = %v, want %v", string(body), tt.wantBody)
				}
			}
		})
	}
}
<|endoftext|>


<commit_before>package output

import (
	"os"
	"testing"
)

func TestNewType(t *testing.T) {
	// We are making sure there are no any panics.
	typ := NewType("test", "./output.go")
	if typ.Package != "test" {
		t.Error("package name was not initialized")
	}
}

func TestNewType_IncorrectPath(t *testing.T) {
	defer expectPanic("when we are not able to read template file, a panic must occur")
	NewType("test", "./pathThatDoesNotExist")
}

func TestCreateDir_ExistingDirectory(t *testing.T) {
	typ := Type{}
	typ.CreateDir("./testdata")
	if typ.Path != "./testdata" {
		t.Error("Type.Path is expected to be initialized")
	}
}

func TestCreateDir_NoWritePrivileges(t *testing.T) {
	typ := Type{}
	defer expectPanic("we have no write privileges for './testdata/readonly', so panic expected")
	typ.CreateDir("./testdata/readonly/something")
}

func TestCreateDir(t *testing.T) {
	typ := Type{}
	typ.CreateDir("./testdata/assets/something")

	// Make sure the directory exists.
	if _, err := os.Stat("./testdata/assets/something"); err != nil {
		if os.IsNotExist(err) {
			t.Errorf("directory was not created, error: '%s'", err)
		}
	}

	// Remove the directories we have created.
	os.RemoveAll("./testdata/assets")
}

func TestGenerate_IncorrectTemplate(t *testing.T) {
	typ := NewType("test", "./testdata/incorrect_template.html")
	typ.Path = "./testdata"
	defer expectPanic("template has errors and thus an error expected")
	typ.Generate()
}

func expectPanic(msg string) {
	if err := recover(); err == nil {
		panic(msg)
	}
}
<commit_msg>Git doesn't save file privileges, so add them manually in tests<commit_after>package output

import (
	"os"
	"testing"
)

// We are using os.Chmod for some tests,
// so it is expected to work on *nix based systems only.
//
//+build linux

func TestNewType(t *testing.T) {
	// We are making sure there are no any panics.
	typ := NewType("test", "./output.go")
	if typ.Package != "test" {
		t.Error("package name was not initialized")
	}
}

func TestNewType_IncorrectPath(t *testing.T) {
	defer expectPanic("when we are not able to read template file, a panic must occur")
	NewType("test", "./pathThatDoesNotExist")
}

func TestCreateDir_ExistingDirectory(t *testing.T) {
	typ := Type{}
	typ.CreateDir("./testdata")
	if typ.Path != "./testdata" {
		t.Error("Type.Path is expected to be initialized")
	}
}

func TestCreateDir_NoWritePrivileges(t *testing.T) {
	// Prepare a readonly directory.
	os.Chmod("./testdata/readonly", 0544)

	typ := Type{}
	defer expectPanic("we have no write privileges for './testdata/readonly', so panic expected")
	typ.CreateDir("./testdata/readonly/something")
}

func TestCreateDir(t *testing.T) {
	typ := Type{}
	typ.CreateDir("./testdata/assets/something")

	// Make sure the directory exists.
	if _, err := os.Stat("./testdata/assets/something"); err != nil {
		if os.IsNotExist(err) {
			t.Errorf("directory was not created, error: '%s'", err)
		}
	}

	// Remove the directories we have created.
	os.RemoveAll("./testdata/assets")
}

func TestGenerate_IncorrectTemplate(t *testing.T) {
	typ := NewType("test", "./testdata/incorrect_template.html")
	typ.Path = "./testdata"
	defer expectPanic("template has errors and thus an error expected")
	typ.Generate()
}

func expectPanic(msg string) {
	if err := recover(); err == nil {
		panic(msg)
	}
}
<|endoftext|>