<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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 server

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/golang/glog"
	"golang.org/x/net/http2"

	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/validation"
)

const (
	defaultKeepAlivePeriod = 3 * time.Minute
)

// serveSecurely runs the secure http server. It fails only if certificates cannot
// be loaded or the initial listen call fails. The actual server loop (stoppable by closing
// stopCh) runs in a go routine, i.e. serveSecurely does not block.
func (s *SecureServingInfo) Serve(handler http.Handler, shutdownTimeout time.Duration, stopCh <-chan struct{}) error {
	if s.Listener == nil {
		return fmt.Errorf("listener must not be nil")
	}

	secureServer := &http.Server{
		Addr:           s.Listener.Addr().String(),
		Handler:        handler,
		MaxHeaderBytes: 1 << 20,
		TLSConfig: &tls.Config{
			NameToCertificate: s.SNICerts,
			// Can't use SSLv3 because of POODLE and BEAST
			// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
			// Can't use TLSv1.1 because of RC4 cipher usage
			MinVersion: tls.VersionTLS12,
			// enable HTTP2 for go's 1.7 HTTP Server
			NextProtos: []string{"h2", "http/1.1"},
		},
	}

	if s.MinTLSVersion > 0 {
		secureServer.TLSConfig.MinVersion = s.MinTLSVersion
	}
	if len(s.CipherSuites) > 0 {
		secureServer.TLSConfig.CipherSuites = s.CipherSuites
	}

	if s.Cert != nil {
		secureServer.TLSConfig.Certificates = []tls.Certificate{*s.Cert}
	}

	// append all named certs. Otherwise, the go tls stack will think no SNI processing
	// is necessary because there is only one cert anyway.
	// Moreover, if ServerCert.CertFile/ServerCert.KeyFile are not set, the first SNI
	// cert will become the default cert. That's what we expect anyway.
	for _, c := range s.SNICerts {
		secureServer.TLSConfig.Certificates = append(secureServer.TLSConfig.Certificates, *c)
	}

	if s.ClientCA != nil {
		// Populate PeerCertificates in requests, but don't reject connections without certificates
		// This allows certificates to be validated by authenticators, while still allowing other auth types
		secureServer.TLSConfig.ClientAuth = tls.RequestClientCert
		// Specify allowed CAs for client certificates
		secureServer.TLSConfig.ClientCAs = s.ClientCA
	}

	if s.HTTP2MaxStreamsPerConnection > 0 {
		http2.ConfigureServer(secureServer, &http2.Server{
			MaxConcurrentStreams: uint32(s.HTTP2MaxStreamsPerConnection),
		})
	}

	glog.Infof("Serving securely on %s", secureServer.Addr)
	return RunServer(secureServer, s.Listener, shutdownTimeout, stopCh)
}

// RunServer listens on the given port if listener is not given,
// then spawns a go-routine continuously serving
// until the stopCh is closed. This function does not block.
// TODO: make private when insecure serving is gone from the kube-apiserver
func RunServer(
	server *http.Server,
	ln net.Listener,
	shutDownTimeout time.Duration,
	stopCh <-chan struct{},
) error {
	if ln == nil {
		return fmt.Errorf("listener must not be nil")
	}

	// Shutdown server gracefully.
	go func() {
		<-stopCh
		ctx, cancel := context.WithTimeout(context.Background(), shutDownTimeout)
		server.Shutdown(ctx)
		cancel()
	}()

	go func() {
		defer utilruntime.HandleCrash()

		var listener net.Listener
		listener = tcpKeepAliveListener{ln.(*net.TCPListener)}
		if server.TLSConfig != nil {
			listener = tls.NewListener(listener, server.TLSConfig)
		}

		err := server.Serve(listener)

		msg := fmt.Sprintf("Stopped listening on %s", ln.Addr().String())
		select {
		case <-stopCh:
			glog.Info(msg)
		default:
			panic(fmt.Sprintf("%s due to error: %v", msg, err))
		}
	}()

	return nil
}

type NamedTLSCert struct {
	TLSCert tls.Certificate

	// names is a list of domain patterns: fully qualified domain names, possibly prefixed with
	// wildcard segments.
	Names []string
}

// getNamedCertificateMap returns a map of *tls.Certificate by name. It's is
// suitable for use in tls.Config#NamedCertificates. Returns an error if any of the certs
// cannot be loaded. Returns nil if len(certs) == 0
func GetNamedCertificateMap(certs []NamedTLSCert) (map[string]*tls.Certificate, error) {
	// register certs with implicit names first, reverse order such that earlier trump over the later
	byName := map[string]*tls.Certificate{}
	for i := len(certs) - 1; i >= 0; i-- {
		if len(certs[i].Names) > 0 {
			continue
		}
		cert := &certs[i].TLSCert

		// read names from certificate common names and DNS names
		if len(cert.Certificate) == 0 {
			return nil, fmt.Errorf("empty SNI certificate, skipping")
		}
		x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
		if err != nil {
			return nil, fmt.Errorf("parse error for SNI certificate: %v", err)
		}
		cn := x509Cert.Subject.CommonName
		if cn == "*" || len(validation.IsDNS1123Subdomain(strings.TrimPrefix(cn, "*."))) == 0 {
			byName[cn] = cert
		}
		for _, san := range x509Cert.DNSNames {
			byName[san] = cert
		}
		// intentionally all IPs in the cert are ignored as SNI forbids passing IPs
		// to select a cert. Before go 1.6 the tls happily passed IPs as SNI values.
	}

	// register certs with explicit names last, overwriting every of the implicit ones,
	// again in reverse order.
	for i := len(certs) - 1; i >= 0; i-- {
		namedCert := &certs[i]
		for _, name := range namedCert.Names {
			byName[name] = &certs[i].TLSCert
		}
	}

	return byName, nil
}

// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// Copied from Go 1.7.2 net/http/server.go
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return nil, err
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(defaultKeepAlivePeriod)
	return tc, nil
}
<commit_msg>UPSTREAM: 67957: Size http2 buffers to allow concurrent streams<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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 server

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/golang/glog"
	"golang.org/x/net/http2"

	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/validation"
)

const (
	defaultKeepAlivePeriod = 3 * time.Minute
)

// serveSecurely runs the secure http server. It fails only if certificates cannot
// be loaded or the initial listen call fails. The actual server loop (stoppable by closing
// stopCh) runs in a go routine, i.e. serveSecurely does not block.
func (s *SecureServingInfo) Serve(handler http.Handler, shutdownTimeout time.Duration, stopCh <-chan struct{}) error {
	if s.Listener == nil {
		return fmt.Errorf("listener must not be nil")
	}

	secureServer := &http.Server{
		Addr:           s.Listener.Addr().String(),
		Handler:        handler,
		MaxHeaderBytes: 1 << 20,
		TLSConfig: &tls.Config{
			NameToCertificate: s.SNICerts,
			// Can't use SSLv3 because of POODLE and BEAST
			// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
			// Can't use TLSv1.1 because of RC4 cipher usage
			MinVersion: tls.VersionTLS12,
			// enable HTTP2 for go's 1.7 HTTP Server
			NextProtos: []string{"h2", "http/1.1"},
		},
	}

	if s.MinTLSVersion > 0 {
		secureServer.TLSConfig.MinVersion = s.MinTLSVersion
	}
	if len(s.CipherSuites) > 0 {
		secureServer.TLSConfig.CipherSuites = s.CipherSuites
	}

	if s.Cert != nil {
		secureServer.TLSConfig.Certificates = []tls.Certificate{*s.Cert}
	}

	// append all named certs. Otherwise, the go tls stack will think no SNI processing
	// is necessary because there is only one cert anyway.
	// Moreover, if ServerCert.CertFile/ServerCert.KeyFile are not set, the first SNI
	// cert will become the default cert. That's what we expect anyway.
	for _, c := range s.SNICerts {
		secureServer.TLSConfig.Certificates = append(secureServer.TLSConfig.Certificates, *c)
	}

	if s.ClientCA != nil {
		// Populate PeerCertificates in requests, but don't reject connections without certificates
		// This allows certificates to be validated by authenticators, while still allowing other auth types
		secureServer.TLSConfig.ClientAuth = tls.RequestClientCert
		// Specify allowed CAs for client certificates
		secureServer.TLSConfig.ClientCAs = s.ClientCA
	}

	// At least 99% of serialized resources in surveyed clusters were smaller than 256kb.
	// This should be big enough to accommodate most API POST requests in a single frame,
	// and small enough to allow a per connection buffer of this size multiplied by `MaxConcurrentStreams`.
	const resourceBody99Percentile = 256 * 1024

	http2Options := &http2.Server{}

	// shrink the per-stream buffer and max framesize from the 1MB default while still accommodating most API POST requests in a single frame
	http2Options.MaxUploadBufferPerStream = resourceBody99Percentile
	http2Options.MaxReadFrameSize = resourceBody99Percentile

	// use the overridden concurrent streams setting or make the default of 250 explicit so we can size MaxUploadBufferPerConnection appropriately
	if s.HTTP2MaxStreamsPerConnection > 0 {
		http2Options.MaxConcurrentStreams = uint32(s.HTTP2MaxStreamsPerConnection)
	} else {
		http2Options.MaxConcurrentStreams = 250
	}

	// increase the connection buffer size from the 1MB default to handle the specified number of concurrent streams
	http2Options.MaxUploadBufferPerConnection = http2Options.MaxUploadBufferPerStream * int32(http2Options.MaxConcurrentStreams)

	// apply settings to the server
	if err := http2.ConfigureServer(secureServer, http2Options); err != nil {
		return fmt.Errorf("error configuring http2: %v", err)
	}

	glog.Infof("Serving securely on %s", secureServer.Addr)
	return RunServer(secureServer, s.Listener, shutdownTimeout, stopCh)
}

// RunServer listens on the given port if listener is not given,
// then spawns a go-routine continuously serving
// until the stopCh is closed. This function does not block.
// TODO: make private when insecure serving is gone from the kube-apiserver
func RunServer(
	server *http.Server,
	ln net.Listener,
	shutDownTimeout time.Duration,
	stopCh <-chan struct{},
) error {
	if ln == nil {
		return fmt.Errorf("listener must not be nil")
	}

	// Shutdown server gracefully.
	go func() {
		<-stopCh
		ctx, cancel := context.WithTimeout(context.Background(), shutDownTimeout)
		server.Shutdown(ctx)
		cancel()
	}()

	go func() {
		defer utilruntime.HandleCrash()

		var listener net.Listener
		listener = tcpKeepAliveListener{ln.(*net.TCPListener)}
		if server.TLSConfig != nil {
			listener = tls.NewListener(listener, server.TLSConfig)
		}

		err := server.Serve(listener)

		msg := fmt.Sprintf("Stopped listening on %s", ln.Addr().String())
		select {
		case <-stopCh:
			glog.Info(msg)
		default:
			panic(fmt.Sprintf("%s due to error: %v", msg, err))
		}
	}()

	return nil
}

type NamedTLSCert struct {
	TLSCert tls.Certificate

	// names is a list of domain patterns: fully qualified domain names, possibly prefixed with
	// wildcard segments.
	Names []string
}

// getNamedCertificateMap returns a map of *tls.Certificate by name. It's is
// suitable for use in tls.Config#NamedCertificates. Returns an error if any of the certs
// cannot be loaded. Returns nil if len(certs) == 0
func GetNamedCertificateMap(certs []NamedTLSCert) (map[string]*tls.Certificate, error) {
	// register certs with implicit names first, reverse order such that earlier trump over the later
	byName := map[string]*tls.Certificate{}
	for i := len(certs) - 1; i >= 0; i-- {
		if len(certs[i].Names) > 0 {
			continue
		}
		cert := &certs[i].TLSCert

		// read names from certificate common names and DNS names
		if len(cert.Certificate) == 0 {
			return nil, fmt.Errorf("empty SNI certificate, skipping")
		}
		x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
		if err != nil {
			return nil, fmt.Errorf("parse error for SNI certificate: %v", err)
		}
		cn := x509Cert.Subject.CommonName
		if cn == "*" || len(validation.IsDNS1123Subdomain(strings.TrimPrefix(cn, "*."))) == 0 {
			byName[cn] = cert
		}
		for _, san := range x509Cert.DNSNames {
			byName[san] = cert
		}
		// intentionally all IPs in the cert are ignored as SNI forbids passing IPs
		// to select a cert. Before go 1.6 the tls happily passed IPs as SNI values.
	}

	// register certs with explicit names last, overwriting every of the implicit ones,
	// again in reverse order.
	for i := len(certs) - 1; i >= 0; i-- {
		namedCert := &certs[i]
		for _, name := range namedCert.Names {
			byName[name] = &certs[i].TLSCert
		}
	}

	return byName, nil
}

// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// Copied from Go 1.7.2 net/http/server.go
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return nil, err
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(defaultKeepAlivePeriod)
	return tc, nil
}
<|endoftext|>


<commit_before>// Package item provides the default functionality to Ponzu's content/data types,
// how they interact with the API, and how to override or enhance their abilities
// using various interfaces.
package item

import (
	"fmt"
	"net/http"
	"regexp"
	"strings"
	"unicode"

	"github.com/blevesearch/bleve"
	"github.com/blevesearch/bleve/mapping"
	"github.com/gofrs/uuid"
	"golang.org/x/text/transform"
	"golang.org/x/text/unicode/norm"
)

// Sluggable makes a struct locatable by URL with it's own path.
// As an Item implementing Sluggable, slugs may overlap. If this is an issue,
// make your content struct (or one which embeds Item) implement Sluggable
// and it will override the slug created by Item's SetSlug with your own
type Sluggable interface {
	SetSlug(string)
	ItemSlug() string
}

// Identifiable enables a struct to have its ID set/get. Typically this is done
// to set an ID to -1 indicating it is new for DB inserts, since by default
// a newly initialized struct would have an ID of 0, the int zero-value, and
// BoltDB's starting key per bucket is 0, thus overwriting the first record.
type Identifiable interface {
	ItemID() int
	SetItemID(int)
	UniqueID() uuid.UUID
	String() string
}

// Sortable ensures data is sortable by time
type Sortable interface {
	Time() int64
	Touch() int64
}

// Hookable provides our user with an easy way to intercept or add functionality
// to the different lifecycles/events a struct may encounter. Item implements
// Hookable with no-ops so our user can override only whichever ones necessary.
type Hookable interface {
	BeforeAPICreate(http.ResponseWriter, *http.Request) error
	AfterAPICreate(http.ResponseWriter, *http.Request) error

	BeforeAPIUpdate(http.ResponseWriter, *http.Request) error
	AfterAPIUpdate(http.ResponseWriter, *http.Request) error

	BeforeAPIDelete(http.ResponseWriter, *http.Request) error
	AfterAPIDelete(http.ResponseWriter, *http.Request) error

	BeforeAdminCreate(http.ResponseWriter, *http.Request) error
	AfterAdminCreate(http.ResponseWriter, *http.Request) error

	BeforeAdminUpdate(http.ResponseWriter, *http.Request) error
	AfterAdminUpdate(http.ResponseWriter, *http.Request) error

	BeforeAdminDelete(http.ResponseWriter, *http.Request) error
	AfterAdminDelete(http.ResponseWriter, *http.Request) error

	BeforeSave(http.ResponseWriter, *http.Request) error
	AfterSave(http.ResponseWriter, *http.Request) error

	BeforeDelete(http.ResponseWriter, *http.Request) error
	AfterDelete(http.ResponseWriter, *http.Request) error

	BeforeApprove(http.ResponseWriter, *http.Request) error
	AfterApprove(http.ResponseWriter, *http.Request) error

	BeforeReject(http.ResponseWriter, *http.Request) error
	AfterReject(http.ResponseWriter, *http.Request) error

	// Enable/Disable used for addons
	BeforeEnable(http.ResponseWriter, *http.Request) error
	AfterEnable(http.ResponseWriter, *http.Request) error

	BeforeDisable(http.ResponseWriter, *http.Request) error
	AfterDisable(http.ResponseWriter, *http.Request) error
}

// Hideable lets a user keep items hidden
type Hideable interface {
	Hide(http.ResponseWriter, *http.Request) error
}

// Pushable lets a user define which values of certain struct fields are
// 'pushed' down to  a client via HTTP/2 Server Push. All items in the slice
// should be the json tag names of the struct fields to which they correspond.
type Pushable interface {
	// the values contained by fields returned by Push must strictly be URL paths
	Push(http.ResponseWriter, *http.Request) ([]string, error)
}

// Omittable lets a user define certin fields within a content struct to remove
// from an API response. Helpful when you want data in the CMS, but not entirely
// shown or available from the content API. All items in the slice should be the
// json tag names of the struct fields to which they correspond.
type Omittable interface {
	Omit(http.ResponseWriter, *http.Request) ([]string, error)
}

// Item should only be embedded into content type structs.
type Item struct {
	UUID      uuid.UUID `json:"uuid"`
	ID        int       `json:"id"`
	Slug      string    `json:"slug"`
	Timestamp int64     `json:"timestamp"`
	Updated   int64     `json:"updated"`
}

// Time partially implements the Sortable interface
func (i Item) Time() int64 {
	return i.Timestamp
}

// Touch partially implements the Sortable interface
func (i Item) Touch() int64 {
	return i.Updated
}

// SetSlug sets the item's slug for its URL
func (i *Item) SetSlug(slug string) {
	i.Slug = slug
}

// ItemSlug sets the item's slug for its URL
func (i *Item) ItemSlug() string {
	return i.Slug
}

// ItemID gets the Item's ID field
// partially implements the Identifiable interface
func (i Item) ItemID() int {
	return i.ID
}

// SetItemID sets the Item's ID field
// partially implements the Identifiable interface
func (i *Item) SetItemID(id int) {
	i.ID = id
}

// UniqueID gets the Item's UUID field
// partially implements the Identifiable interface
func (i Item) UniqueID() uuid.UUID {
	return i.UUID
}

// String formats an Item into a printable value
// partially implements the Identifiable interface
func (i Item) String() string {
	return fmt.Sprintf("Item ID: %s", i.UniqueID())
}

// BeforeAPICreate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAPICreate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAPICreate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAPICreate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAPIUpdate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAPIUpdate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAPIUpdate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAPIUpdate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAPIDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAPIDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAPIDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAPIDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAdminCreate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAdminCreate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAdminCreate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAdminCreate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAdminUpdate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAdminUpdate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAdminUpdate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAdminUpdate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAdminDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAdminDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAdminDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAdminDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeSave is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeSave(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterSave is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterSave(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeApprove is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeApprove(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterApprove is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterApprove(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeReject is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeReject(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterReject is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterReject(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeEnable is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeEnable(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterEnable is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterEnable(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeDisable is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeDisable(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterDisable is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterDisable(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// SearchMapping returns a default implementation of a Bleve IndexMappingImpl
// partially implements search.Searchable
func (i Item) SearchMapping() (*mapping.IndexMappingImpl, error) {
	mapping := bleve.NewIndexMapping()
	mapping.StoreDynamic = false

	return mapping, nil
}

// IndexContent determines if a type should be indexed for searching
// partially implements search.Searchable
func (i Item) IndexContent() bool {
	return false
}

// Slug returns a URL friendly string from the title of a post item
func Slug(i Identifiable) (string, error) {
	// get the name of the post item
	name := strings.TrimSpace(i.String())

	// filter out non-alphanumeric character or non-whitespace
	slug, err := stringToSlug(name)
	if err != nil {
		return "", err
	}

	return slug, nil
}

func isMn(r rune) bool {
	return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
}

// modified version of: https://www.socketloop.com/tutorials/golang-format-strings-to-seo-friendly-url-example
func stringToSlug(s string) (string, error) {
	src := []byte(strings.ToLower(s))

	// convert all spaces to dash
	rx := regexp.MustCompile("[[:space:]]")
	src = rx.ReplaceAll(src, []byte("-"))

	// remove all blanks such as tab
	rx = regexp.MustCompile("[[:blank:]]")
	src = rx.ReplaceAll(src, []byte(""))

	rx = regexp.MustCompile("[!/:-@[-`{-~]")
	src = rx.ReplaceAll(src, []byte(""))

	rx = regexp.MustCompile("/[^\x20-\x7F]/")
	src = rx.ReplaceAll(src, []byte(""))

	rx = regexp.MustCompile("`&(amp;)?#?[a-z0-9]+;`i")
	src = rx.ReplaceAll(src, []byte("-"))

	rx = regexp.MustCompile("`&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);`i")
	src = rx.ReplaceAll(src, []byte("\\1"))

	rx = regexp.MustCompile("`[^a-z0-9]`i")
	src = rx.ReplaceAll(src, []byte("-"))

	rx = regexp.MustCompile("`[-]+`")
	src = rx.ReplaceAll(src, []byte("-"))

	str := strings.Replace(string(src), "'", "", -1)
	str = strings.Replace(str, `"`, "", -1)
	str = strings.Replace(str, "&", "-", -1)

	t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
	slug, _, err := transform.String(t, str)
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(slug), nil
}

// NormalizeString removes and replaces illegal characters for URLs and other
// path entities. Useful for taking user input and converting it for keys or URLs.
func NormalizeString(s string) (string, error) {
	return stringToSlug(s)
}
<commit_msg>Compile regex once instead of each time function is called.<commit_after>// Package item provides the default functionality to Ponzu's content/data types,
// how they interact with the API, and how to override or enhance their abilities
// using various interfaces.
package item

import (
	"fmt"
	"net/http"
	"regexp"
	"strings"
	"unicode"

	"github.com/blevesearch/bleve"
	"github.com/blevesearch/bleve/mapping"
	"github.com/gofrs/uuid"
	"golang.org/x/text/transform"
	"golang.org/x/text/unicode/norm"
)

var rxList map[*regexp.Regexp][]byte

func init() {
	// Compile regex once to use in stringToSlug().
	// We store the compiled regex as the key
	// and assign the replacement as the map's value.
	rxList = map[*regexp.Regexp][]byte{
		regexp.MustCompile("`[-]+`"):                  []byte("-"),
		regexp.MustCompile("[[:space:]]"):             []byte("-"),
		regexp.MustCompile("[[:blank:]]"):             []byte(""),
		regexp.MustCompile("`[^a-z0-9]`i"):            []byte("-"),
		regexp.MustCompile("[!/:-@[-`{-~]"):           []byte(""),
		regexp.MustCompile("/[^\x20-\x7F]/"):          []byte(""),
		regexp.MustCompile("`&(amp;)?#?[a-z0-9]+;`i"): []byte("-"),
		regexp.MustCompile("`&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);`i"): []byte("\\1"),
	}
}

// Sluggable makes a struct locatable by URL with it's own path.
// As an Item implementing Sluggable, slugs may overlap. If this is an issue,
// make your content struct (or one which embeds Item) implement Sluggable
// and it will override the slug created by Item's SetSlug with your own
type Sluggable interface {
	SetSlug(string)
	ItemSlug() string
}

// Identifiable enables a struct to have its ID set/get. Typically this is done
// to set an ID to -1 indicating it is new for DB inserts, since by default
// a newly initialized struct would have an ID of 0, the int zero-value, and
// BoltDB's starting key per bucket is 0, thus overwriting the first record.
type Identifiable interface {
	ItemID() int
	SetItemID(int)
	UniqueID() uuid.UUID
	String() string
}

// Sortable ensures data is sortable by time
type Sortable interface {
	Time() int64
	Touch() int64
}

// Hookable provides our user with an easy way to intercept or add functionality
// to the different lifecycles/events a struct may encounter. Item implements
// Hookable with no-ops so our user can override only whichever ones necessary.
type Hookable interface {
	BeforeAPICreate(http.ResponseWriter, *http.Request) error
	AfterAPICreate(http.ResponseWriter, *http.Request) error

	BeforeAPIUpdate(http.ResponseWriter, *http.Request) error
	AfterAPIUpdate(http.ResponseWriter, *http.Request) error

	BeforeAPIDelete(http.ResponseWriter, *http.Request) error
	AfterAPIDelete(http.ResponseWriter, *http.Request) error

	BeforeAdminCreate(http.ResponseWriter, *http.Request) error
	AfterAdminCreate(http.ResponseWriter, *http.Request) error

	BeforeAdminUpdate(http.ResponseWriter, *http.Request) error
	AfterAdminUpdate(http.ResponseWriter, *http.Request) error

	BeforeAdminDelete(http.ResponseWriter, *http.Request) error
	AfterAdminDelete(http.ResponseWriter, *http.Request) error

	BeforeSave(http.ResponseWriter, *http.Request) error
	AfterSave(http.ResponseWriter, *http.Request) error

	BeforeDelete(http.ResponseWriter, *http.Request) error
	AfterDelete(http.ResponseWriter, *http.Request) error

	BeforeApprove(http.ResponseWriter, *http.Request) error
	AfterApprove(http.ResponseWriter, *http.Request) error

	BeforeReject(http.ResponseWriter, *http.Request) error
	AfterReject(http.ResponseWriter, *http.Request) error

	// Enable/Disable used for addons
	BeforeEnable(http.ResponseWriter, *http.Request) error
	AfterEnable(http.ResponseWriter, *http.Request) error

	BeforeDisable(http.ResponseWriter, *http.Request) error
	AfterDisable(http.ResponseWriter, *http.Request) error
}

// Hideable lets a user keep items hidden
type Hideable interface {
	Hide(http.ResponseWriter, *http.Request) error
}

// Pushable lets a user define which values of certain struct fields are
// 'pushed' down to  a client via HTTP/2 Server Push. All items in the slice
// should be the json tag names of the struct fields to which they correspond.
type Pushable interface {
	// the values contained by fields returned by Push must strictly be URL paths
	Push(http.ResponseWriter, *http.Request) ([]string, error)
}

// Omittable lets a user define certin fields within a content struct to remove
// from an API response. Helpful when you want data in the CMS, but not entirely
// shown or available from the content API. All items in the slice should be the
// json tag names of the struct fields to which they correspond.
type Omittable interface {
	Omit(http.ResponseWriter, *http.Request) ([]string, error)
}

// Item should only be embedded into content type structs.
type Item struct {
	UUID      uuid.UUID `json:"uuid"`
	ID        int       `json:"id"`
	Slug      string    `json:"slug"`
	Timestamp int64     `json:"timestamp"`
	Updated   int64     `json:"updated"`
}

// Time partially implements the Sortable interface
func (i Item) Time() int64 {
	return i.Timestamp
}

// Touch partially implements the Sortable interface
func (i Item) Touch() int64 {
	return i.Updated
}

// SetSlug sets the item's slug for its URL
func (i *Item) SetSlug(slug string) {
	i.Slug = slug
}

// ItemSlug sets the item's slug for its URL
func (i *Item) ItemSlug() string {
	return i.Slug
}

// ItemID gets the Item's ID field
// partially implements the Identifiable interface
func (i Item) ItemID() int {
	return i.ID
}

// SetItemID sets the Item's ID field
// partially implements the Identifiable interface
func (i *Item) SetItemID(id int) {
	i.ID = id
}

// UniqueID gets the Item's UUID field
// partially implements the Identifiable interface
func (i Item) UniqueID() uuid.UUID {
	return i.UUID
}

// String formats an Item into a printable value
// partially implements the Identifiable interface
func (i Item) String() string {
	return fmt.Sprintf("Item ID: %s", i.UniqueID())
}

// BeforeAPICreate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAPICreate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAPICreate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAPICreate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAPIUpdate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAPIUpdate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAPIUpdate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAPIUpdate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAPIDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAPIDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAPIDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAPIDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAdminCreate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAdminCreate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAdminCreate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAdminCreate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAdminUpdate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAdminUpdate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAdminUpdate is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAdminUpdate(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeAdminDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeAdminDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterAdminDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterAdminDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeSave is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeSave(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterSave is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterSave(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterDelete(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeApprove is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeApprove(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterApprove is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterApprove(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeReject is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeReject(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterReject is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterReject(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeEnable is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeEnable(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterEnable is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterEnable(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// BeforeDisable is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeDisable(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// AfterDisable is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterDisable(res http.ResponseWriter, req *http.Request) error {
	return nil
}

// SearchMapping returns a default implementation of a Bleve IndexMappingImpl
// partially implements search.Searchable
func (i Item) SearchMapping() (*mapping.IndexMappingImpl, error) {
	mapping := bleve.NewIndexMapping()
	mapping.StoreDynamic = false

	return mapping, nil
}

// IndexContent determines if a type should be indexed for searching
// partially implements search.Searchable
func (i Item) IndexContent() bool {
	return false
}

// Slug returns a URL friendly string from the title of a post item
func Slug(i Identifiable) (string, error) {
	// get the name of the post item
	name := strings.TrimSpace(i.String())

	// filter out non-alphanumeric character or non-whitespace
	slug, err := stringToSlug(name)
	if err != nil {
		return "", err
	}

	return slug, nil
}

func isMn(r rune) bool {
	return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
}

// modified version of: https://www.socketloop.com/tutorials/golang-format-strings-to-seo-friendly-url-example
func stringToSlug(s string) (string, error) {
	src := []byte(strings.ToLower(s))

	// Range over compiled regex and replacements from init().
	for rx := range rxList {
		src = rx.ReplaceAll(src, rxList[rx])
	}

	str := strings.Replace(string(src), "'", "", -1)
	str = strings.Replace(str, `"`, "", -1)
	str = strings.Replace(str, "&", "-", -1)

	t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
	slug, _, err := transform.String(t, str)
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(slug), nil
}

// NormalizeString removes and replaces illegal characters for URLs and other
// path entities. Useful for taking user input and converting it for keys or URLs.
func NormalizeString(s string) (string, error) {
	return stringToSlug(s)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/oauth2"

	"github.com/google/go-github/github"
	"github.com/howeyc/gopass"
)

var (
	client   *github.Client
	gistFile = filepath.Join(os.Getenv("HOME"), ".gist")
	ctx      = context.Background()
)

func init() {
	dt, err := ioutil.ReadFile(gistFile)
	if err != nil {
		log.Printf("*WARNING*: `%v`, you are Anonymous!", err)
		client = github.NewClient(nil)
	} else {
		ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(dt)})
		tc := oauth2.NewClient(ctx, ts)
		client = github.NewClient(tc)
	}
}

// Gist stands for gist related ops.
type Gist struct {
	*github.Client
}

// Create makes a gist.
func (g *Gist) Create(description string, anonymous, public bool, files ...string) (err error) {
	fs := make(map[github.GistFilename]github.GistFile, len(files))
	for _, v := range files {
		dat, err := ioutil.ReadFile(v)
		if err != nil {
			return err
		}
		c := string(dat)
		vv := strings.Split(v, "/")
		name := vv[len(vv)-1]
		fs[github.GistFilename(name)] = github.GistFile{Filename: &name, Content: &c}
	}
	g0 := &github.Gist{Files: fs, Public: &public, Description: &description}
	if anonymous {
		*g.Client = *github.NewClient(nil)
	}
	g0, _, err = g.Gists.Create(ctx, g0)
	if err == nil {
		fmt.Println(*g0.HTMLURL)
	}
	return
}

// List gets user's gists.
func (g *Gist) List(user string, public bool) (err error) {
	opt := &github.GistListOptions{
		ListOptions: github.ListOptions{
			PerPage: 20,
		},
	}
	for {
		gs, resp, err := g.Gists.List(ctx, user, opt)
		if err != nil {
			return err
		}
		for _, i := range gs {
			if public && *i.Public {
				continue
			}

			for fn := range i.Files {
				fmt.Printf("%-64s%s\n", *i.HTMLURL, fn)
			}
		}
		if resp.NextPage == 0 {
			break
		}
		opt.ListOptions.Page = resp.NextPage
	}
	return
}

// Get querys a single gist detail.
func (g *Gist) Get(id string) (err error) {
	g0, _, err := g.Gists.Get(ctx, id)
	if err != nil {
		return
	}
	fmt.Println(strings.Repeat("-", 100))
	for _, f := range g0.Files {
		fmt.Printf("%v\t%v\n\n%v\n", *f.Filename, *f.Size, *f.Content)
		fmt.Println(strings.Repeat("-", 100))
	}
	return
}

// Delete deletes gaven gists by ids.
func (g *Gist) Delete(id ...string) error {
	c := make(chan error, len(id))
	for _, i := range id {
		go func(id string) {
			_, err := g.Gists.Delete(ctx, id)
			if err == nil {
				fmt.Printf("<id: %s> has been deleted ...\n", id)
			}
			c <- err
		}(i)
	}
	for i := 0; i < len(id); i++ {
		if err := <-c; err != nil {
			fmt.Println(err)
		}
	}
	return nil
}

// Token is a GitHub token entry.
type Token struct {
	ID  int    `json:"id"`
	URL string `json:"url"`
	App struct {
		Name     string `json:"name"`
		URL      string `json:"url"`
		ClientID string `json:"client_id"`
	} `json:"app"`
	Token          string      `json:"token"`
	HashedToken    string      `json:"hashed_token"`
	TokenLastEight string      `json:"token_last_eight"`
	Note           string      `json:"note"`
	NoteURL        interface{} `json:"note_url"`
	CreatedAt      time.Time   `json:"created_at"`
	UpdatedAt      time.Time   `json:"updated_at"`
	Scopes         []string    `json:"scopes"`
	Fingerprint    interface{} `json:"fingerprint"`
}

func ask() (user, pass string) {
	fmt.Print("GitHub username: ")
	if _, err := fmt.Scan(&user); err != nil {
		return
	}
	fmt.Print("GitHub password: ")
	p, err := gopass.GetPasswdMasked()
	if err != nil {
		return
	}
	pass = string(p)
	return

}

func basicRequest(user, pass, otp string) (*http.Request, error) {
	fp := time.Now().Nanosecond()
	note := fmt.Sprintf(`{"note": "gist","scopes":["gist"],"fingerprint":"%v"}`, fp)
	url := "https://api.github.com/authorizations"
	req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(note)))
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(user, pass)
	req.Header.Set("Content-Type", "application/json")
	if len(otp) != 0 {
		req.Header.Set("X-GitHub-OTP", otp)
	}
	return req, nil
}

func token(user, pass string) (err error) {
	req, err := basicRequest(user, pass, "")
	if err != nil {
		return nil
	}
	client := http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if strings.HasPrefix(resp.Header.Get("X-Github-Otp"), "required") {
		var code string
		fmt.Print("GitHub OTP: ")
		fmt.Scan(&code)
		req, err := basicRequest(user, pass, code)
		if err != nil {
			return nil
		}
		resp, err = client.Do(req)
	}
	if err != nil {
		return
	}

	if sc := resp.StatusCode; sc == http.StatusUnauthorized {
		return errors.New(http.StatusText(sc))
	}

	defer resp.Body.Close()

	var t Token
	if err := json.NewDecoder(resp.Body).Decode(&t); err != nil {
		return err
	}

	if err := ioutil.WriteFile(gistFile, []byte(t.Token), 0644); err != nil {
		return err
	}
	fmt.Println("success ...")
	return nil
}
<commit_msg>Single gist show supports url<commit_after>package main

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/oauth2"

	"github.com/google/go-github/github"
	"github.com/howeyc/gopass"
)

var (
	client   *github.Client
	gistFile = filepath.Join(os.Getenv("HOME"), ".gist")
	ctx      = context.Background()
)

func init() {
	dt, err := ioutil.ReadFile(gistFile)
	if err != nil {
		log.Printf("*WARNING*: `%v`, you are Anonymous!", err)
		client = github.NewClient(nil)
	} else {
		ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(dt)})
		tc := oauth2.NewClient(ctx, ts)
		client = github.NewClient(tc)
	}
}

// Gist stands for gist related ops.
type Gist struct {
	*github.Client
}

// Create makes a gist.
func (g *Gist) Create(description string, anonymous, public bool, files ...string) (err error) {
	fs := make(map[github.GistFilename]github.GistFile, len(files))
	for _, v := range files {
		dat, err := ioutil.ReadFile(v)
		if err != nil {
			return err
		}
		c := string(dat)
		vv := strings.Split(v, "/")
		name := vv[len(vv)-1]
		fs[github.GistFilename(name)] = github.GistFile{Filename: &name, Content: &c}
	}
	g0 := &github.Gist{Files: fs, Public: &public, Description: &description}
	if anonymous {
		*g.Client = *github.NewClient(nil)
	}
	g0, _, err = g.Gists.Create(ctx, g0)
	if err == nil {
		fmt.Println(*g0.HTMLURL)
	}
	return
}

// List gets user's gists.
func (g *Gist) List(user string, public bool) (err error) {
	opt := &github.GistListOptions{
		ListOptions: github.ListOptions{
			PerPage: 20,
		},
	}
	for {
		gs, resp, err := g.Gists.List(ctx, user, opt)
		if err != nil {
			return err
		}
		for _, i := range gs {
			if public && *i.Public {
				continue
			}

			for fn := range i.Files {
				fmt.Printf("%-64s%s\n", *i.HTMLURL, fn)
			}
		}
		if resp.NextPage == 0 {
			break
		}
		opt.ListOptions.Page = resp.NextPage
	}
	return
}

// Get querys a single gist detail.
func (g *Gist) Get(id string) (err error) {
	if strings.HasPrefix(id, "https") {
		ids := strings.Split(id, "/")
		id = ids[len(ids)-1]
	}
	g0, _, err := g.Gists.Get(ctx, id)
	if err != nil {
		return
	}
	fmt.Println(strings.Repeat("-", 100))
	for _, f := range g0.Files {
		fmt.Printf("%v\t%v\n\n%v\n", *f.Filename, *f.Size, *f.Content)
		fmt.Println(strings.Repeat("-", 100))
	}
	return
}

// Delete deletes gaven gists by ids.
func (g *Gist) Delete(id ...string) error {
	c := make(chan error, len(id))
	for _, i := range id {
		go func(id string) {
			_, err := g.Gists.Delete(ctx, id)
			if err == nil {
				fmt.Printf("<id: %s> has been deleted ...\n", id)
			}
			c <- err
		}(i)
	}
	for i := 0; i < len(id); i++ {
		if err := <-c; err != nil {
			fmt.Println(err)
		}
	}
	return nil
}

// Token is a GitHub token entry.
type Token struct {
	ID  int    `json:"id"`
	URL string `json:"url"`
	App struct {
		Name     string `json:"name"`
		URL      string `json:"url"`
		ClientID string `json:"client_id"`
	} `json:"app"`
	Token          string      `json:"token"`
	HashedToken    string      `json:"hashed_token"`
	TokenLastEight string      `json:"token_last_eight"`
	Note           string      `json:"note"`
	NoteURL        interface{} `json:"note_url"`
	CreatedAt      time.Time   `json:"created_at"`
	UpdatedAt      time.Time   `json:"updated_at"`
	Scopes         []string    `json:"scopes"`
	Fingerprint    interface{} `json:"fingerprint"`
}

func ask() (user, pass string) {
	fmt.Print("GitHub username: ")
	if _, err := fmt.Scan(&user); err != nil {
		return
	}
	fmt.Print("GitHub password: ")
	p, err := gopass.GetPasswdMasked()
	if err != nil {
		return
	}
	pass = string(p)
	return

}

func basicRequest(user, pass, otp string) (*http.Request, error) {
	fp := time.Now().Nanosecond()
	note := fmt.Sprintf(`{"note": "gist","scopes":["gist"],"fingerprint":"%v"}`, fp)
	url := "https://api.github.com/authorizations"
	req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(note)))
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(user, pass)
	req.Header.Set("Content-Type", "application/json")
	if len(otp) != 0 {
		req.Header.Set("X-GitHub-OTP", otp)
	}
	return req, nil
}

func token(user, pass string) (err error) {
	req, err := basicRequest(user, pass, "")
	if err != nil {
		return nil
	}
	client := http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if strings.HasPrefix(resp.Header.Get("X-Github-Otp"), "required") {
		var code string
		fmt.Print("GitHub OTP: ")
		fmt.Scan(&code)
		req, err := basicRequest(user, pass, code)
		if err != nil {
			return nil
		}
		resp, err = client.Do(req)
	}
	if err != nil {
		return
	}

	if sc := resp.StatusCode; sc == http.StatusUnauthorized {
		return errors.New(http.StatusText(sc))
	}

	defer resp.Body.Close()

	var t Token
	if err := json.NewDecoder(resp.Body).Decode(&t); err != nil {
		return err
	}

	if err := ioutil.WriteFile(gistFile, []byte(t.Token), 0644); err != nil {
		return err
	}
	fmt.Println("success ...")
	return nil
}
<|endoftext|>


<commit_before>// This code is part of the touka

package gist

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

// GIT_IO_URL is the github's URL shortner
// API v3 is the current version of Github API
const (
	GITHUB_API_URL = "https://api.github.com/gists"
	GIT_IO_URL = "http://git.io"
	GIT_BASE_PATH = "/api/v3"
)

var (
	USER_AGENT = "gist/#/touka"
	token = os.Getenv ("GITHUB_TOKEN")
)

var (
	publicFlag bool
	description string
	anonymous bool
	responseObj map [string]interface{}
)

type GistFile struct {
	Content string `json:\"content\"`
}

type Gist struct {
	Description string                  `json:\"description\"`
	publicFile  bool                    `json:\"public\"`
	GistFile    map [string] GistFile   `json:\"files\"`
}

func loadTokenFromFIle () (token string) {
	file := os.Getenv ("$HOME") + "/.gist"
	github_token, err := ioutil.ReadFile (file)
	if err != nil {
		log.Fatal (err)
	}
	return string (github_token)
}

func Push (file Gist) {
	pfile, err := json.Marshal (file)

	if err {
		log.Fatal ("[ !! ] Cannot marshal json: ", err)
	}

	b := bytes.NewBuffer (pfile)
	fmt.Println ("[ ** ] Uploading...")

	req, err := http.NewRequest ("POST", GITHUB_API_URL, b)

	if !anonymous {
		req.Header.Set ("Content-Type", "application/json")
		req.Headet.Set ("Accept", "application/json")
		req.SetBasicAuth (token, "x-oauth-basic")
	}

}
<commit_msg>gist user list<commit_after>// This code is part of the touka
package Gist

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"

	"github.com/fatih/color"
)

// GIT_IO_URL is the github's URL shortner
// API v3 is the current version of Github API
const (
	GITHUB_API_URL = "https://api.github.com/"
	GIT_IO_URL = "http://git.io"
	GIT_BASE_PATH = "/api/v3"
)

var (
	USER_AGENT = "gist/#/touka"
	token = os.Getenv ("GITHUB_TOKEN")
)

var (
	publicFlag bool
	description string
	anonymous bool
	responseObj map [string]interface{}
)

/* Color initialization */
var (
	yellow := color.New (color.FgYellow).SprintfFunc ()
	yellow_printf := color.New (color.FgYellow).PrintfFunc ()

	green := color.New (color.FgGreen).SprintfFunc ()
	green_printf := color.New (color.FgGreen).PrintfFunc ()
)

type GistFile struct {
	Content string `json:\"content\"`
}

type Gist struct {
	Description string                  `json:\"description\"`
	publicFile  bool                    `json:\"public\"`
	GistFile    map [string] GistFile   `json:\"files\"`
}

/* Load Token From ~/.gist */
func loadTokenFromFIle () (token string) {
	file := os.Getenv ("$HOME") + "/.gist"
	github_token, err := ioutil.ReadFile (file)
	if err != nil {
		log.Fatal (err)
	}
	return string (github_token)
}

/* Error handling */
func errhandler (handler err) {
	if handler != nil {
		log.Fatal (handler)
		os.Exit (1)
	}
}

/* Push the file on gist.github.com */
func Push (file Gist) {
	pfile, err := json.Marshal (file)

	if err {
		log.Fatal ("[ !! ] Cannot marshal json: ", err)
	}

	b := bytes.NewBuffer (pfile)
	fmt.Println ("[ ** ] Uploading...")

	req, err := http.NewRequest ("POST", GITHUB_API_URL, b)

	if !anonymous {
		req.Header.Set ("Content-Type", "application/json")
		req.Headet.Set ("Accept", "application/json")
		req.SetBasicAuth (token, "x-oauth-basic")
	}
}

func List (url, token string) {

	if token != "" {
		url = url + "?access_token=" + token
	}

	res, err := http.Get (url)
	errhandler (err)

	defer res.Body.Close ()
	body, err := ioutil.ReadAll (res.Body)
	errhandler (err)

	var jsonRes []GistJSON.Response
	err= json.Unmarshal (body, &jsonRes)
	errhandler (err)

	for _, val := range jsonRes {
	    yellow_printf ("%sn", val.HtmlUrl)
		fmt.Printf ("(%s)\t%s\n", shortDate (val.CreatedAt), val.Description)
		fmt.Printf ("\n")
	}

}
<|endoftext|>


<commit_before>package udpapi

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	"encoding/gob"
)

func init() {
	// implements error
	gob.RegisterName("udpapi.APIError", APIError{})
	// implements APIReply
	gob.RegisterName("udpapi.errorWrapper", errorWrapper{})
	// implements APIReply
	gob.RegisterName("udpapi.genericReply", genericReply{})
}

type APIError struct {
	Code int
	Desc string
}

func (err *APIError) Error() string {
	return fmt.Sprint(err.Code, err.Desc)
}

// The interface for all UDP API replies.
//
// The user should call Error() to verify if the API call completed successfully.
type APIReply interface {
	// An opaque string used as identifying tag.
	Tag() string

	// The integer code for the reply.
	Code() int

	// The description for the reply (first line minus code).
	Text() string

	// Slice with all lines of the reply.
	Lines() []string

	// Indicates whether the network code detected truncation.
	Truncated() bool

	// Returns the underlying error, if any.
	Error() error
}

type errorWrapper struct {
	err error
}

func (_ *errorWrapper) Tag() string {
	return ""
}

func (w *errorWrapper) Code() int {
	switch e := w.err.(type) {
	case *APIError:
		return e.Code
	default:
		return 999
	}
}

func (w *errorWrapper) Text() string {
	switch e := w.err.(type) {
	case *APIError:
		return e.Desc
	default:
		return e.Error()
	}
}

func (w *errorWrapper) Lines() []string {
	return []string{w.Text()}
}

func (_ *errorWrapper) Truncated() bool {
	return false
}

func (w *errorWrapper) Error() error {
	return w.err
}

func newErrorWrapper(err error) APIReply {
	return &errorWrapper{
		err: err,
	}
}

type genericReply struct {
	raw       []byte
	text      string
	lines     []string
	tag       string
	code      int
	truncated bool
	err       error
}

// The value APIReply.Error() returns after a timeout.
// Unrelated to the server-side error 604 TIMEOUT - DELAY AND RESUBMIT.
var TimeoutError = errors.New("Timeout")

func newGenericReply(raw []byte) (r *genericReply) {
	str := string(raw)
	lines := strings.Split(str, "\n")
	parts := strings.Fields(lines[0])

	// invalid packet
	if len(parts) < 1 {
		return nil
	}

	// Drop lines that are only whitespace
	for len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" {
		lines = lines[:len(lines)-1]
	}

	// XXX: REQUIRES that the tag is not parsable as a base 10 number.
	// Just prepending any sent tag with 'T' ought to be enough
	tag := ""
	text := ""
	code, err := strconv.ParseInt(parts[0], 10, 16)
	if err != nil {
		tag = parts[0]
		code, err = strconv.ParseInt(parts[1], 10, 16)

		if len(parts) > 2 {
			text = strings.Join(parts[2:], " ")
		}
	} else if len(parts) > 1 {
		text = strings.Join(parts[1:], " ")
	}

	var e *APIError = nil
	// 720-799 range is for notifications
	// 799 is an API server shutdown notice, so I guess it's okay to be an error
	if code < 200 || (code > 299 && code < 720) || code > 798 {
		e = &APIError{Code: int(code), Desc: text}
	}

	return &genericReply{
		tag:   tag,
		code:  int(code),
		text:  text,
		lines: lines,
		err:   e,
	}
}

func (r *genericReply) Tag() string {
	return r.tag
}

func (r *genericReply) Code() int {
	return r.code
}

func (r *genericReply) Text() string {
	return r.text
}

func (r *genericReply) Lines() []string {
	return r.lines
}

func (r *genericReply) Truncated() bool {
	return r.truncated
}

func (r *genericReply) Error() error {
	return r.err
}
<commit_msg>udpapi: Avoid out-of-bounds access on malformed packets<commit_after>package udpapi

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	"encoding/gob"
)

func init() {
	// implements error
	gob.RegisterName("udpapi.APIError", APIError{})
	// implements APIReply
	gob.RegisterName("udpapi.errorWrapper", errorWrapper{})
	// implements APIReply
	gob.RegisterName("udpapi.genericReply", genericReply{})
}

type APIError struct {
	Code int
	Desc string
}

func (err *APIError) Error() string {
	return fmt.Sprint(err.Code, err.Desc)
}

// The interface for all UDP API replies.
//
// The user should call Error() to verify if the API call completed successfully.
type APIReply interface {
	// An opaque string used as identifying tag.
	Tag() string

	// The integer code for the reply.
	Code() int

	// The description for the reply (first line minus code).
	Text() string

	// Slice with all lines of the reply.
	Lines() []string

	// Indicates whether the network code detected truncation.
	Truncated() bool

	// Returns the underlying error, if any.
	Error() error
}

type errorWrapper struct {
	err error
}

func (_ *errorWrapper) Tag() string {
	return ""
}

func (w *errorWrapper) Code() int {
	switch e := w.err.(type) {
	case *APIError:
		return e.Code
	default:
		return 999
	}
}

func (w *errorWrapper) Text() string {
	switch e := w.err.(type) {
	case *APIError:
		return e.Desc
	default:
		return e.Error()
	}
}

func (w *errorWrapper) Lines() []string {
	return []string{w.Text()}
}

func (_ *errorWrapper) Truncated() bool {
	return false
}

func (w *errorWrapper) Error() error {
	return w.err
}

func newErrorWrapper(err error) APIReply {
	return &errorWrapper{
		err: err,
	}
}

type genericReply struct {
	raw       []byte
	text      string
	lines     []string
	tag       string
	code      int
	truncated bool
	err       error
}

// The value APIReply.Error() returns after a timeout.
// Unrelated to the server-side error 604 TIMEOUT - DELAY AND RESUBMIT.
var TimeoutError = errors.New("Timeout")

func newGenericReply(raw []byte) (r *genericReply) {
	str := string(raw)
	lines := strings.Split(str, "\n")
	parts := strings.Fields(lines[0])

	// invalid packet
	if len(parts) < 1 {
		return nil
	}

	// Drop lines that are only whitespace
	for len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" {
		lines = lines[:len(lines)-1]
	}

	// XXX: REQUIRES that the tag is not parsable as a base 10 number.
	// Just prepending any sent tag with 'T' ought to be enough
	tag := ""
	text := ""
	code, err := strconv.ParseInt(parts[0], 10, 16)
	if err != nil && len(parts) > 1 {
		tag = parts[0]
		code, err = strconv.ParseInt(parts[1], 10, 16)

		if len(parts) > 2 {
			text = strings.Join(parts[2:], " ")
		}
	} else if len(parts) > 1 {
		text = strings.Join(parts[1:], " ")
	}

	e := err
	// 720-799 range is for notifications
	// 799 is an API server shutdown notice, so I guess it's okay to be an error
	if err == nil && code < 200 || (code > 299 && code < 720) || code > 798 {
		e = &APIError{Code: int(code), Desc: text}
	}

	return &genericReply{
		tag:   tag,
		code:  int(code),
		text:  text,
		lines: lines,
		err:   e,
	}
}

func (r *genericReply) Tag() string {
	return r.tag
}

func (r *genericReply) Code() int {
	return r.code
}

func (r *genericReply) Text() string {
	return r.text
}

func (r *genericReply) Lines() []string {
	return r.lines
}

func (r *genericReply) Truncated() bool {
	return r.truncated
}

func (r *genericReply) Error() error {
	return r.err
}
<|endoftext|>


<commit_before>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   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 main

import (
	"github.com/vmware/harbor/api"
	"github.com/vmware/harbor/controllers"
	"github.com/vmware/harbor/service"
	"github.com/vmware/harbor/service/token"

	"github.com/astaxie/beego"
)

func initRouters() {

	beego.SetStaticPath("registry/static/i18n", "static/i18n")
	beego.SetStaticPath("registry/static/resources", "static/resources")
	beego.SetStaticPath("registry/static/vendors", "static/vendors")

	beego.Router("/login", &controllers.CommonController{}, "post:Login")
	beego.Router("/logout", &controllers.CommonController{}, "get:Logout")
	beego.Router("/language", &controllers.CommonController{}, "get:SwitchLanguage")
	beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists")
	beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")
	beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail")

	beego.Router("/", &controllers.IndexController{})
	beego.Router("/signIn", &controllers.SignInController{})
	beego.Router("/register", &controllers.RegisterController{})
	beego.Router("/addUser", &controllers.AddUserController{})
	beego.Router("/forgotPassword", &controllers.ForgotPasswordController{})
	beego.Router("/resetPassword", &controllers.ResetPasswordController{})
	beego.Router("/changePassword", &controllers.ChangePasswordController{})

	beego.Router("/registry/project", &controllers.ProjectController{})
	beego.Router("/registry/detail", &controllers.ItemDetailController{})

	beego.Router("/search", &controllers.SearchController{})

	//API:
	beego.Router("/api/search", &api.SearchAPI{})
	beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
	beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List")
	beego.Router("/api/projects/?:id", &api.ProjectAPI{})
	beego.Router("/api/projects/:id/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
	beego.Router("/api/statistics", &api.StatisticAPI{})
	beego.Router("/api/projects/:id([0-9]+)/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
	beego.Router("/api/users/?:id", &api.UserAPI{})
	beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
	beego.Router("/api/repositories", &api.RepositoryAPI{})
	beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
	beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
	beego.Router("/api/jobs/replication/?:id([0-9]+)", &api.RepJobAPI{})
	beego.Router("/api/jobs/replication/:id([0-9]+)/log", &api.RepJobAPI{}, "get:GetLog")
	beego.Router("/api/policies/replication/:id([0-9]+)", &api.RepPolicyAPI{})
	beego.Router("/api/policies/replication", &api.RepPolicyAPI{}, "get:List")
	beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &api.RepPolicyAPI{}, "put:UpdateEnablement")
	beego.Router("/api/targets/", &api.TargetAPI{}, "get:List")
	beego.Router("/api/targets/:id([0-9]+)", &api.TargetAPI{})
	beego.Router("/api/targets/ping", &api.TargetAPI{}, "post:Ping")
	beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
	beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
	beego.Router("api/logs", &api.LogAPI{})

	//external service that hosted on harbor process:
	beego.Router("/service/notifications", &service.NotificationHandler{})
	beego.Router("/service/token", &token.Handler{})
}
<commit_msg>modify router<commit_after>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   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 main

import (
	"github.com/vmware/harbor/api"
	"github.com/vmware/harbor/controllers"
	"github.com/vmware/harbor/service"
	"github.com/vmware/harbor/service/token"

	"github.com/astaxie/beego"
)

func initRouters() {

	beego.SetStaticPath("registry/static/i18n", "static/i18n")
	beego.SetStaticPath("registry/static/resources", "static/resources")
	beego.SetStaticPath("registry/static/vendors", "static/vendors")

	beego.Router("/login", &controllers.CommonController{}, "post:Login")
	beego.Router("/logout", &controllers.CommonController{}, "get:Logout")
	beego.Router("/language", &controllers.CommonController{}, "get:SwitchLanguage")
	beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists")
	beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")
	beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail")

	beego.Router("/", &controllers.IndexController{})
	beego.Router("/signIn", &controllers.SignInController{})
	beego.Router("/register", &controllers.RegisterController{})
	beego.Router("/addUser", &controllers.AddUserController{})
	beego.Router("/forgotPassword", &controllers.ForgotPasswordController{})
	beego.Router("/resetPassword", &controllers.ResetPasswordController{})
	beego.Router("/changePassword", &controllers.ChangePasswordController{})

	beego.Router("/registry/project", &controllers.ProjectController{})
	beego.Router("/registry/detail", &controllers.ItemDetailController{})

	beego.Router("/search", &controllers.SearchController{})

	//API:
	beego.Router("/api/search", &api.SearchAPI{})
	beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
	beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List")
	beego.Router("/api/projects/?:id", &api.ProjectAPI{})
	beego.Router("/api/projects/:id/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
	beego.Router("/api/statistics", &api.StatisticAPI{})
	beego.Router("/api/projects/:id([0-9]+)/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
	beego.Router("/api/users/?:id", &api.UserAPI{})
	beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
	beego.Router("/api/repositories", &api.RepositoryAPI{})
	beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
	beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
	beego.Router("/api/jobs/replication/?:id([0-9]+)", &api.RepJobAPI{})
	beego.Router("/api/jobs/replication/:id([0-9]+)/log", &api.RepJobAPI{}, "get:GetLog")
	beego.Router("/api/policies/replication/:id([0-9]+)", &api.RepPolicyAPI{})
	beego.Router("/api/policies/replication", &api.RepPolicyAPI{}, "get:List")
	beego.Router("/api/policies/replication", &api.RepPolicyAPI{}, "post:Post")
	beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &api.RepPolicyAPI{}, "put:UpdateEnablement")
	beego.Router("/api/targets/", &api.TargetAPI{}, "get:List")
	beego.Router("/api/targets/", &api.TargetAPI{}, "post:Post")
	beego.Router("/api/targets/:id([0-9]+)", &api.TargetAPI{})
	beego.Router("/api/targets/ping", &api.TargetAPI{}, "post:Ping")
	beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
	beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
	beego.Router("api/logs", &api.LogAPI{})

	//external service that hosted on harbor process:
	beego.Router("/service/notifications", &service.NotificationHandler{})
	beego.Router("/service/token", &token.Handler{})
}
<|endoftext|>


<commit_before>// Package user holds details about a person who is using the application
package user

import (
	"context"
	"net/http"

	"github.com/google/uuid"

	"github.com/gilcrest/go-api-basic/domain/errs"
	"github.com/gilcrest/go-api-basic/domain/org"
	"github.com/gilcrest/go-api-basic/domain/person"
)

// User holds details of a User from Google
type User struct {
	// ID: unique identifier of the User
	ID uuid.UUID

	// username: unique (within an Org) username of the User
	Username string

	// org: Org user is associated with.
	Org org.Org

	// profile: The profile of the user
	Profile person.Profile
}

// IsValid determines whether the User has proper data to be considered valid
func (u User) IsValid() bool {
	switch {
	case u.Username == "":
		return false
	case u.Profile.FirstName == "":
		return false
	case u.Profile.LastName == "":
		return false
	}
	return true
}

type contextKey string

const contextKeyUser = contextKey("user")

// FromRequest gets the User from the request
func FromRequest(r *http.Request) (User, error) {
	u, ok := r.Context().Value(contextKeyUser).(User)
	if !ok {
		return u, errs.E(errs.Internal, "User not set properly to context")
	}
	if !u.IsValid() {
		return u, errs.E(errs.Internal, "User empty in context")
	}
	return u, nil
}

// CtxWithUser sets the User to the given context
func CtxWithUser(ctx context.Context, u User) context.Context {
	return context.WithValue(ctx, contextKeyUser, u)
}
<commit_msg>add NullID method<commit_after>// Package user holds details about a person who is using the application
package user

import (
	"context"
	"net/http"

	"github.com/google/uuid"

	"github.com/gilcrest/go-api-basic/domain/errs"
	"github.com/gilcrest/go-api-basic/domain/org"
	"github.com/gilcrest/go-api-basic/domain/person"
)

// User holds details of a User from various providers
type User struct {
	// ID: unique identifier of the User
	ID uuid.UUID

	// username: unique (within an Org) username of the User
	Username string

	// org: Org user is associated with.
	Org org.Org

	// profile: The profile of the user
	Profile person.Profile
}

// NullID returns ID as uuid.NullUUID
func (u User) NullID() uuid.NullUUID {
	if u.ID == uuid.Nil {
		return uuid.NullUUID{}
	}
	return uuid.NullUUID{
		UUID:  u.ID,
		Valid: true,
	}
}

// IsValid determines whether the User has proper data to be considered valid
func (u User) IsValid() bool {
	switch {
	case u.Username == "":
		return false
	case u.Profile.FirstName == "":
		return false
	case u.Profile.LastName == "":
		return false
	}
	return true
}

type contextKey string

const contextKeyUser = contextKey("user")

// FromRequest gets the User from the request
func FromRequest(r *http.Request) (User, error) {
	u, ok := r.Context().Value(contextKeyUser).(User)
	if !ok {
		return u, errs.E(errs.Internal, "User not set properly to context")
	}
	if !u.IsValid() {
		return u, errs.E(errs.Internal, "User empty in context")
	}
	return u, nil
}

// CtxWithUser sets the User to the given context
func CtxWithUser(ctx context.Context, u User) context.Context {
	return context.WithValue(ctx, contextKeyUser, u)
}
<|endoftext|>


<commit_before>// Copyright 2014 Hajime Hoshi
//
// 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 ebiten

import (
	"fmt"
	"math"
	"sync"
	"sync/atomic"

	"github.com/hajimehoshi/ebiten/internal/buffered"
	"github.com/hajimehoshi/ebiten/internal/clock"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/hooks"
)

type defaultGame struct {
	update  func(screen *Image) error
	width   int
	height  int
	context *uiContext
}

func (d *defaultGame) Update(screen *Image) error {
	return d.update(screen)
}

func (d *defaultGame) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	// Ignore the outside size.
	d.context.m.Lock()
	w, h := d.width, d.height
	d.context.m.Unlock()
	return w, h
}

type uiContext struct {
	game      Game
	offscreen *Image
	screen    *Image

	// scaleForWindow is the scale of a window. This doesn't represent the scale on fullscreen. This value works
	// only on desktops.
	//
	// scaleForWindow is for backward compatibility and is used to calculate the window size when SetScreenSize
	// is called.
	scaleForWindow float64

	outsideSizeUpdated bool
	outsideWidth       float64
	outsideHeight      float64

	err atomic.Value

	m sync.Mutex
}

var theUIContext = &uiContext{}

func (c *uiContext) set(game Game, scaleForWindow float64) {
	c.m.Lock()
	defer c.m.Unlock()
	c.game = game

	if g, ok := game.(*defaultGame); ok {
		c.scaleForWindow = scaleForWindow
		g.context = c
	}
}

func (c *uiContext) setError(err error) {
	c.err.Store(err)
}

func (c *uiContext) setScaleForWindow(scale float64) {
	c.m.Lock()
	defer c.m.Unlock()

	if c.game == nil {
		panic("ebiten: setScaleForWindow can be called only after the main loop starts")
	}

	g, ok := c.game.(*defaultGame)
	if !ok {
		panic("ebiten: setScaleForWindow can be called only when Run is used")
	}

	if w := uiDriver().Window(); w != nil {
		ww, wh := g.width, g.height
		c.scaleForWindow = scale
		w.SetSize(int(float64(ww)*scale), int(float64(wh)*scale))
	}
}

func (c *uiContext) getScaleForWindow() float64 {
	c.m.Lock()
	defer c.m.Unlock()

	if c.game == nil {
		panic("ebiten: getScaleForWindow can be called only after the main loop starts")
	}

	if _, ok := c.game.(*defaultGame); !ok {
		panic("ebiten: getScaleForWindow can be called only when Run is used")
	}
	s := c.scaleForWindow
	return s
}

// setScreenSize sets the (logical) screen size and adjusts the window size.
//
// setScreenSize is for backward compatibility. This is called from ebiten.SetScreenSize.
func (c *uiContext) setScreenSize(width, height int) {
	c.m.Lock()
	defer c.m.Unlock()

	if c.game == nil {
		panic("ebiten: SetScreenSize can be called only after the main loop starts")
	}

	g, ok := c.game.(*defaultGame)
	if !ok {
		panic("ebiten: SetScreenSize can be called only when Run is used")
	}

	g.width = width
	g.height = height
	if w := uiDriver().Window(); w != nil {
		s := c.scaleForWindow
		w.SetSize(int(float64(width)*s), int(float64(height)*s))
	}
}

func (c *uiContext) Layout(outsideWidth, outsideHeight float64) {
	c.outsideSizeUpdated = true
	c.outsideWidth = outsideWidth
	c.outsideHeight = outsideHeight
}

func (c *uiContext) updateOffscreen() {
	sw, sh := c.game.Layout(int(c.outsideWidth), int(c.outsideHeight))
	if sw <= 0 || sh <= 0 {
		panic("ebiten: Layout must return positive numbers")
	}

	if c.offscreen != nil && !c.outsideSizeUpdated {
		if w, h := c.offscreen.Size(); w == sw && h == sh {
			return
		}
	}
	c.outsideSizeUpdated = false

	if c.screen != nil {
		_ = c.screen.Dispose()
		c.screen = nil
	}

	if c.offscreen != nil {
		if w, h := c.offscreen.Size(); w != sw || h != sh {
			_ = c.offscreen.Dispose()
			c.offscreen = nil
		}
	}
	if c.offscreen == nil {
		c.offscreen = newImage(sw, sh, FilterDefault, true)
	}

	// The window size is automatically adjusted when Run is used.
	if _, ok := c.game.(*defaultGame); ok {
		c.setScreenSize(sw, sh)
	}

	// TODO: This is duplicated with mobile/ebitenmobileview/funcs.go. Refactor this.
	d := uiDriver().DeviceScaleFactor()
	c.screen = newScreenFramebufferImage(int(c.outsideWidth*d), int(c.outsideHeight*d))

	// Do not have to update scaleForWindow since this is used only for backward compatibility.
	// Then, if a window is resizable, scaleForWindow (= ebiten.ScreenScale) might not match with the actual
	// scale. This is fine since ebiten.ScreenScale will be deprecated.
}

func (c *uiContext) setWindowResizable(resizable bool) {
	c.m.Lock()
	defer c.m.Unlock()

	if resizable && c.game != nil {
		if _, ok := c.game.(*defaultGame); ok {
			panic("ebiten: a resizable window works with RunGame, not Run")
		}
	}
	if w := uiDriver().Window(); w != nil {
		w.SetResizable(resizable)
	}
}

func (c *uiContext) screenScale() float64 {
	if c.offscreen == nil {
		return 0
	}
	sw, sh := c.offscreen.Size()
	d := uiDriver().DeviceScaleFactor()
	scaleX := c.outsideWidth / float64(sw) * d
	scaleY := c.outsideHeight / float64(sh) * d
	return math.Min(scaleX, scaleY)
}

func (c *uiContext) offsets() (float64, float64) {
	if c.offscreen == nil {
		return 0, 0
	}
	sw, sh := c.offscreen.Size()
	d := uiDriver().DeviceScaleFactor()
	s := c.screenScale()
	width := float64(sw) * s
	height := float64(sh) * s
	return (c.outsideWidth*d - width) / 2, (c.outsideHeight*d - height) / 2
}

func (c *uiContext) Update(afterFrameUpdate func()) error {
	// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.

	if err, ok := c.err.Load().(error); ok && err != nil {
		return err
	}
	if err := buffered.BeginFrame(); err != nil {
		return err
	}
	if err := c.update(afterFrameUpdate); err != nil {
		return err
	}
	if err := buffered.EndFrame(); err != nil {
		return err
	}
	return nil
}

func (c *uiContext) Draw() error {
	if err, ok := c.err.Load().(error); ok && err != nil {
		return err
	}
	if err := buffered.BeginFrame(); err != nil {
		return err
	}
	c.draw()
	if err := buffered.EndFrame(); err != nil {
		return err
	}
	return nil
}

func (c *uiContext) update(afterFrameUpdate func()) error {
	updateCount := clock.Update(MaxTPS())

	if _, ok := c.game.(interface{ Draw(*Image) }); ok {
		for i := 0; i < updateCount; i++ {
			c.updateOffscreen()

			// Rendering should be always skipped.
			setDrawingSkipped(true)

			if err := hooks.RunBeforeUpdateHooks(); err != nil {
				return err
			}

			// Multiple successive Clear call should be integrated into one graphics command, then
			// calling Clear on every Update should not affect the performance.
			c.offscreen.Clear()
			if err := c.game.Update(c.offscreen); err != nil {
				return err
			}
			uiDriver().Input().ResetForFrame()
			afterFrameUpdate()
		}
	} else {
		for i := 0; i < updateCount; i++ {
			c.updateOffscreen()

			c.offscreen.Clear()

			setDrawingSkipped(i < updateCount-1)

			if err := hooks.RunBeforeUpdateHooks(); err != nil {
				return err
			}
			if err := c.game.Update(c.offscreen); err != nil {
				return err
			}
			uiDriver().Input().ResetForFrame()
			afterFrameUpdate()
		}
	}
	return nil
}

func (c *uiContext) draw() {
	// c.screen might be nil when updateCount is 0 in the initial state (#1039).
	if c.screen == nil {
		return
	}

	if game, ok := c.game.(interface{ Draw(*Image) }); ok {
		c.offscreen.Clear()
		game.Draw(c.offscreen)
	}

	// This clear is needed for fullscreen mode or some mobile platforms (#622).
	c.screen.Clear()

	op := &DrawImageOptions{}

	s := c.screenScale()
	switch vd := uiDriver().Graphics().VDirection(); vd {
	case driver.VDownward:
		// c.screen is special: its Y axis is down to up,
		// and the origin point is lower left.
		op.GeoM.Scale(s, -s)
		_, h := c.offscreen.Size()
		op.GeoM.Translate(0, float64(h)*s)
	case driver.VUpward:
		op.GeoM.Scale(s, s)
	default:
		panic(fmt.Sprintf("ebiten: invalid v-direction: %d", vd))
	}

	op.GeoM.Translate(c.offsets())
	op.CompositeMode = CompositeModeCopy

	// filterScreen works with >=1 scale, but does not well with <1 scale.
	// Use regular FilterLinear instead so far (#669).
	if s >= 1 {
		op.Filter = filterScreen
	} else {
		op.Filter = FilterLinear
	}
	_ = c.screen.DrawImage(c.offscreen, op)
}

func (c *uiContext) AdjustPosition(x, y float64) (float64, float64) {
	d := uiDriver().DeviceScaleFactor()
	ox, oy := c.offsets()
	s := c.screenScale()
	return (x*d - ox) / s, (y*d - oy) / s
}
<commit_msg>ui: Refactoring<commit_after>// Copyright 2014 Hajime Hoshi
//
// 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 ebiten

import (
	"fmt"
	"math"
	"sync"
	"sync/atomic"

	"github.com/hajimehoshi/ebiten/internal/buffered"
	"github.com/hajimehoshi/ebiten/internal/clock"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/hooks"
)

type defaultGame struct {
	update  func(screen *Image) error
	width   int
	height  int
	context *uiContext
}

func (d *defaultGame) Update(screen *Image) error {
	return d.update(screen)
}

func (d *defaultGame) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	// Ignore the outside size.
	d.context.m.Lock()
	w, h := d.width, d.height
	d.context.m.Unlock()
	return w, h
}

type uiContext struct {
	game      Game
	offscreen *Image
	screen    *Image

	// scaleForWindow is the scale of a window. This doesn't represent the scale on fullscreen. This value works
	// only on desktops.
	//
	// scaleForWindow is for backward compatibility and is used to calculate the window size when SetScreenSize
	// is called.
	scaleForWindow float64

	outsideSizeUpdated bool
	outsideWidth       float64
	outsideHeight      float64

	err atomic.Value

	m sync.Mutex
}

var theUIContext = &uiContext{}

func (c *uiContext) set(game Game, scaleForWindow float64) {
	c.m.Lock()
	defer c.m.Unlock()
	c.game = game

	if g, ok := game.(*defaultGame); ok {
		c.scaleForWindow = scaleForWindow
		g.context = c
	}
}

func (c *uiContext) setError(err error) {
	c.err.Store(err)
}

func (c *uiContext) setScaleForWindow(scale float64) {
	c.m.Lock()
	defer c.m.Unlock()

	if c.game == nil {
		panic("ebiten: setScaleForWindow can be called only after the main loop starts")
	}

	g, ok := c.game.(*defaultGame)
	if !ok {
		panic("ebiten: setScaleForWindow can be called only when Run is used")
	}

	if w := uiDriver().Window(); w != nil {
		ww, wh := g.width, g.height
		c.scaleForWindow = scale
		w.SetSize(int(float64(ww)*scale), int(float64(wh)*scale))
	}
}

func (c *uiContext) getScaleForWindow() float64 {
	c.m.Lock()
	defer c.m.Unlock()

	if c.game == nil {
		panic("ebiten: getScaleForWindow can be called only after the main loop starts")
	}

	if _, ok := c.game.(*defaultGame); !ok {
		panic("ebiten: getScaleForWindow can be called only when Run is used")
	}
	s := c.scaleForWindow
	return s
}

// setScreenSize sets the (logical) screen size and adjusts the window size.
//
// setScreenSize is for backward compatibility. This is called from ebiten.SetScreenSize.
func (c *uiContext) setScreenSize(width, height int) {
	c.m.Lock()
	defer c.m.Unlock()

	if c.game == nil {
		panic("ebiten: SetScreenSize can be called only after the main loop starts")
	}

	g, ok := c.game.(*defaultGame)
	if !ok {
		panic("ebiten: SetScreenSize can be called only when Run is used")
	}

	g.width = width
	g.height = height
	if w := uiDriver().Window(); w != nil {
		s := c.scaleForWindow
		w.SetSize(int(float64(width)*s), int(float64(height)*s))
	}
}

func (c *uiContext) Layout(outsideWidth, outsideHeight float64) {
	c.outsideSizeUpdated = true
	c.outsideWidth = outsideWidth
	c.outsideHeight = outsideHeight
}

func (c *uiContext) updateOffscreen() {
	sw, sh := c.game.Layout(int(c.outsideWidth), int(c.outsideHeight))
	if sw <= 0 || sh <= 0 {
		panic("ebiten: Layout must return positive numbers")
	}

	if c.offscreen != nil && !c.outsideSizeUpdated {
		if w, h := c.offscreen.Size(); w == sw && h == sh {
			return
		}
	}
	c.outsideSizeUpdated = false

	if c.screen != nil {
		_ = c.screen.Dispose()
		c.screen = nil
	}

	if c.offscreen != nil {
		if w, h := c.offscreen.Size(); w != sw || h != sh {
			_ = c.offscreen.Dispose()
			c.offscreen = nil
		}
	}
	if c.offscreen == nil {
		c.offscreen = newImage(sw, sh, FilterDefault, true)
	}

	// The window size is automatically adjusted when Run is used.
	if _, ok := c.game.(*defaultGame); ok {
		c.setScreenSize(sw, sh)
	}

	// TODO: This is duplicated with mobile/ebitenmobileview/funcs.go. Refactor this.
	d := uiDriver().DeviceScaleFactor()
	c.screen = newScreenFramebufferImage(int(c.outsideWidth*d), int(c.outsideHeight*d))

	// Do not have to update scaleForWindow since this is used only for backward compatibility.
	// Then, if a window is resizable, scaleForWindow (= ebiten.ScreenScale) might not match with the actual
	// scale. This is fine since ebiten.ScreenScale will be deprecated.
}

func (c *uiContext) setWindowResizable(resizable bool) {
	c.m.Lock()
	defer c.m.Unlock()

	if resizable && c.game != nil {
		if _, ok := c.game.(*defaultGame); ok {
			panic("ebiten: a resizable window works with RunGame, not Run")
		}
	}
	if w := uiDriver().Window(); w != nil {
		w.SetResizable(resizable)
	}
}

func (c *uiContext) screenScale() float64 {
	if c.offscreen == nil {
		return 0
	}
	sw, sh := c.offscreen.Size()
	d := uiDriver().DeviceScaleFactor()
	scaleX := c.outsideWidth / float64(sw) * d
	scaleY := c.outsideHeight / float64(sh) * d
	return math.Min(scaleX, scaleY)
}

func (c *uiContext) offsets() (float64, float64) {
	if c.offscreen == nil {
		return 0, 0
	}
	sw, sh := c.offscreen.Size()
	d := uiDriver().DeviceScaleFactor()
	s := c.screenScale()
	width := float64(sw) * s
	height := float64(sh) * s
	return (c.outsideWidth*d - width) / 2, (c.outsideHeight*d - height) / 2
}

func (c *uiContext) Update(afterFrameUpdate func()) error {
	// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.

	if err, ok := c.err.Load().(error); ok && err != nil {
		return err
	}
	if err := buffered.BeginFrame(); err != nil {
		return err
	}
	if err := c.update(afterFrameUpdate); err != nil {
		return err
	}
	if err := buffered.EndFrame(); err != nil {
		return err
	}
	return nil
}

func (c *uiContext) Draw() error {
	if err, ok := c.err.Load().(error); ok && err != nil {
		return err
	}
	if err := buffered.BeginFrame(); err != nil {
		return err
	}
	c.draw()
	if err := buffered.EndFrame(); err != nil {
		return err
	}
	return nil
}

func (c *uiContext) update(afterFrameUpdate func()) error {
	_, hasDraw := c.game.(interface{ Draw(*Image) })

	updateCount := clock.Update(MaxTPS())
	for i := 0; i < updateCount; i++ {
		c.updateOffscreen()

		// When Draw exists, rendering should be always skipped.
		setDrawingSkipped(hasDraw || i < updateCount-1)

		if err := hooks.RunBeforeUpdateHooks(); err != nil {
			return err
		}

		// Multiple successive Clear call should be integrated into one graphics command, then
		// calling Clear on every Update should not affect the performance.
		c.offscreen.Clear()
		if err := c.game.Update(c.offscreen); err != nil {
			return err
		}
		uiDriver().Input().ResetForFrame()
		afterFrameUpdate()
	}
	return nil
}

func (c *uiContext) draw() {
	// c.screen might be nil when updateCount is 0 in the initial state (#1039).
	if c.screen == nil {
		return
	}

	if game, ok := c.game.(interface{ Draw(*Image) }); ok {
		c.offscreen.Clear()
		game.Draw(c.offscreen)
	}

	// This clear is needed for fullscreen mode or some mobile platforms (#622).
	c.screen.Clear()

	op := &DrawImageOptions{}

	s := c.screenScale()
	switch vd := uiDriver().Graphics().VDirection(); vd {
	case driver.VDownward:
		// c.screen is special: its Y axis is down to up,
		// and the origin point is lower left.
		op.GeoM.Scale(s, -s)
		_, h := c.offscreen.Size()
		op.GeoM.Translate(0, float64(h)*s)
	case driver.VUpward:
		op.GeoM.Scale(s, s)
	default:
		panic(fmt.Sprintf("ebiten: invalid v-direction: %d", vd))
	}

	op.GeoM.Translate(c.offsets())
	op.CompositeMode = CompositeModeCopy

	// filterScreen works with >=1 scale, but does not well with <1 scale.
	// Use regular FilterLinear instead so far (#669).
	if s >= 1 {
		op.Filter = filterScreen
	} else {
		op.Filter = FilterLinear
	}
	_ = c.screen.DrawImage(c.offscreen, op)
}

func (c *uiContext) AdjustPosition(x, y float64) (float64, float64) {
	d := uiDriver().DeviceScaleFactor()
	ox, oy := c.offsets()
	s := c.screenScale()
	return (x*d - ox) / s, (y*d - oy) / s
}
<|endoftext|>


<commit_before>package main

import (
	"strings"

	"database/sql"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/metrics"
	"github.com/piotrkowalczuk/charon"
	"github.com/piotrkowalczuk/mnemosyne"
	"github.com/piotrkowalczuk/sklog"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/peer"
)

type handler struct {
	logger     log.Logger
	monitor    monitoringRPC
	session    mnemosyne.Mnemosyne
	repository struct {
		user            UserRepository
		userGroups      UserGroupsRepository
		userPermissions UserPermissionsRepository
		permission      PermissionRepository
		group           GroupRepository
	}
}

func newHandler(rs *rpcServer, ctx context.Context, endpoint string) *handler {
	h := &handler{
		logger:     rs.loggerBackground(ctx, "endpoint", endpoint),
		session:    rs.session,
		repository: rs.repository,
	}
	if rs.monitor.enabled {
		h.monitor = rs.monitor.rpc.with(metrics.Field{Key: "endpoint", Value: endpoint})
	}

	return h
}

func (h *handler) handle(err error, msg string) {
	if err != nil {
		code := grpc.Code(err)

		h.loggerWith("code", code)
		if h.monitor.enabled {
			h.monitor.errors.With(metrics.Field{Key: "code", Value: code.String()}).Add(1)
		}
		sklog.Error(h.logger, err)

		return
	}

	sklog.Debug(h.logger, msg)
}

func (h *handler) loggerWith(keyval ...interface{}) {
	h.logger = log.NewContext(h.logger).With(keyval...)
}

func (h *handler) retrieveActor(ctx context.Context) (a *actor, err error) {
	var (
		userID   int64
		entities []*permissionEntity
		ses      *mnemosyne.Session
	)

	ses, err = h.session.FromContext(ctx)
	if err != nil {
		// TODO: make it better ;(
		if peer, ok := peer.FromContext(ctx); ok {
			if strings.HasPrefix(peer.Addr.String(), "127.0.0.1") {
				return &actor{}, nil
			}
		}
		return
	}
	userID, err = charon.SubjectID(ses.SubjectId).UserID()
	if err != nil {
		return
	}

	a = &actor{}
	a.user, err = h.repository.user.FindOneByID(userID)
	if err != nil {
		return
	}

	entities, err = h.repository.permission.FindByUserID(userID)
	if err != nil {
		if err == sql.ErrNoRows {
			return a, nil
		}
		return
	}

	a.permissions = make(charon.Permissions, 0, len(entities))
	for _, e := range entities {
		a.permissions = append(a.permissions, e.Permission())
	}

	return
}

func (h *handler) addRequest(i uint64) {
	if h.monitor.enabled {
		h.monitor.requests.Add(i)
	}
}
<commit_msg>handler nil pointer exception fix<commit_after>package main

import (
	"strings"

	"database/sql"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/metrics"
	"github.com/piotrkowalczuk/charon"
	"github.com/piotrkowalczuk/mnemosyne"
	"github.com/piotrkowalczuk/sklog"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/peer"
)

type handler struct {
	logger     log.Logger
	monitor    monitoringRPC
	session    mnemosyne.Mnemosyne
	repository struct {
		user            UserRepository
		userGroups      UserGroupsRepository
		userPermissions UserPermissionsRepository
		permission      PermissionRepository
		group           GroupRepository
	}
}

func newHandler(rs *rpcServer, ctx context.Context, endpoint string) *handler {
	h := &handler{
		logger:     rs.loggerBackground(ctx, "endpoint", endpoint),
		session:    rs.session,
		repository: rs.repository,
	}
	if rs.monitor.enabled {
		h.monitor = rs.monitor.rpc.with(metrics.Field{Key: "endpoint", Value: endpoint})
	}

	return h
}

func (h *handler) handle(err error, msg string) {
	if err != nil {
		code := grpc.Code(err)

		h.loggerWith("code", code)
		if h.monitor.enabled {
			h.monitor.errors.With(metrics.Field{Key: "code", Value: code.String()}).Add(1)
		}
		sklog.Error(h.logger, err)

		return
	}

	sklog.Debug(h.logger, msg)
}

func (h *handler) loggerWith(keyval ...interface{}) {
	h.logger = log.NewContext(h.logger).With(keyval...)
}

func (h *handler) retrieveActor(ctx context.Context) (akt *actor, err error) {
	var (
		userID   int64
		entities []*permissionEntity
		ses      *mnemosyne.Session
	)

	ses, err = h.session.FromContext(ctx)
	if err != nil {
		// TODO: make it better ;(
		if peer, ok := peer.FromContext(ctx); ok {
			if strings.HasPrefix(peer.Addr.String(), "127.0.0.1") {
				return &actor{}, nil
			}
		}
		return
	}
	userID, err = charon.SubjectID(ses.SubjectId).UserID()
	if err != nil {
		return
	}

	akt = &actor{}
	akt.user, err = h.repository.user.FindOneByID(userID)
	if err != nil {
		return
	}

	entities, err = h.repository.permission.FindByUserID(userID)
	if err != nil {
		if err == sql.ErrNoRows {
			return akt, nil
		}
		return
	}

	akt.permissions = make(charon.Permissions, 0, len(entities))
	for _, e := range entities {
		akt.permissions = append(akt.permissions, e.Permission())
	}

	return
}

func (h *handler) addRequest(i uint64) {
	if h.monitor.enabled {
		h.monitor.requests.Add(i)
	}
}
<|endoftext|>