<commit_before>package namedwebsockets

import (
	"encoding/base64"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"text/template"

	"github.com/richtr/bcrypt"
	tls "github.com/richtr/go-tls-srp"
)

var (
	// Regular expression matchers

	serviceNameRegexStr = "[A-Za-z0-9/\\+=\\*\\._-]{1,255}"

	peerIdRegexStr = "[0-9]{4,}"

	isValidLocalRequest = regexp.MustCompile(fmt.Sprintf("^((/control)?/(network|local)/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isValidBroadcastRequest = regexp.MustCompile(fmt.Sprintf("^(/(network|local)/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isNetworkServiceRequest = regexp.MustCompile(fmt.Sprintf("^((/control)?/network/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isControlServiceRequest = regexp.MustCompile(fmt.Sprintf("^(/control/(network|local)/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isNetworkControlServiceRequest = regexp.MustCompile(fmt.Sprintf("^(/control/network/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isValidServiceName = regexp.MustCompile(fmt.Sprintf("^%s$", serviceNameRegexStr))

	// TLS-SRP configuration components

	// We deliberately only use a weak salt because we don't persistently store TLS-SRP credential data
	Salt = []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}

	serviceTab = CredentialsStore(map[string]string{})
)

type NamedWebSocket_Service struct {
	Host string
	Port int

	ProxyPort int

	localSockets   *NamedWebSocket_Service_Group
	networkSockets *NamedWebSocket_Service_Group
}

type NamedWebSocket_Service_Group struct {
	// All Named WebSocket services (local or network) that this service manages
	Services map[string]*NamedWebSocket

	// Discovery related trackers for services advertised and registered
	knownServiceNames map[string]bool

	AdvertisedServiceHashes map[string]bool

	ResolvedServiceRecords   map[string]*NamedWebSocket_DNSRecord
	UnresolvedServiceRecords map[string]*NamedWebSocket_DNSRecord
}

func NewNamedWebSocketService(host string, port int) *NamedWebSocket_Service {
	service := &NamedWebSocket_Service{
		Host: host,
		Port: port,

		ProxyPort: 0,

		localSockets:   NewNamedWebSocketServiceGroup(),
		networkSockets: NewNamedWebSocketServiceGroup(),
	}
	return service
}

func NewNamedWebSocketServiceGroup() *NamedWebSocket_Service_Group {
	return &NamedWebSocket_Service_Group{
		Services:                make(map[string]*NamedWebSocket),
		knownServiceNames:       make(map[string]bool),
		AdvertisedServiceHashes: make(map[string]bool),

		ResolvedServiceRecords:   make(map[string]*NamedWebSocket_DNSRecord),
		UnresolvedServiceRecords: make(map[string]*NamedWebSocket_DNSRecord),
	}
}

func (service *NamedWebSocket_Service) StartHTTPServer(async bool) {
	// Create a new custom http server multiplexer
	serveMux := http.NewServeMux()

	// Serve the test console
	serveMux.HandleFunc("/", service.serveConsoleTemplate)

	// Serve websocket creation endpoints for localhost clients
	serveMux.HandleFunc("/local/", service.serveLocalWSCreator)
	serveMux.HandleFunc("/network/", service.serveLocalWSCreator)

	// Serve websocket control endpoint for localhost clients
	serveMux.HandleFunc("/control/", service.serveLocalWSCreator)

	// Listen and on loopback address + port
	listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", service.Port))
	if err != nil {
		log.Fatal("Could not serve proxy. ", err)
	}

	log.Printf("Serving Named WebSockets Proxy at http://localhost:%d/", service.Port)

	if async {
		go http.Serve(listener, serveMux)
	} else {
		http.Serve(listener, serveMux)
	}
}

func (service *NamedWebSocket_Service) StartProxyServer() {
	// Create a new custom http server multiplexer
	serveMux := http.NewServeMux()

	// Serve secure websocket creation endpoints for network clients
	serveMux.HandleFunc("/local/", service.serveProxyWSCreator)
	serveMux.HandleFunc("/network/", service.serveProxyWSCreator)

	// Generate random server salt for use in TLS-SRP data storage
	var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	b := make([]rune, 32)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	srpSaltKey := string(b)

	tlsServerConfig := &tls.Config{
		SRPLookup:   serviceTab,
		SRPSaltKey:  srpSaltKey,
		SRPSaltSize: len(Salt),
	}

	// Listen on all addresses + port
	tlsSrpListener, err := tls.Listen("tcp", ":0", tlsServerConfig)
	if err != nil {
		log.Fatal(err)
	}

	// Obtain and store the port of the proxy endpoint
	_, port, err := net.SplitHostPort(tlsSrpListener.Addr().String())
	if err != nil {
		log.Fatal(err)
	}

	service.ProxyPort, _ = strconv.Atoi(port)

	log.Printf("Serving Named WebSockets Federation Server at wss://%s:%d/", service.Host, service.ProxyPort)

	http.Serve(tlsSrpListener, serveMux)
}

func (service *NamedWebSocket_Service) StartDiscoveryServers(timeoutSeconds int) {
	networkDiscoveryServer := NewDiscoveryServer("network")

	defer networkDiscoveryServer.Shutdown()

	go func() {
		localDiscoveryServer := NewDiscoveryServer("local")

		defer localDiscoveryServer.Shutdown()

		for !localDiscoveryServer.closed {
			localDiscoveryServer.Browse(service, timeoutSeconds)
		}
	}()

	log.Print("Listening for named websocket service advertisements...")

	for !networkDiscoveryServer.closed {
		networkDiscoveryServer.Browse(service, timeoutSeconds)
	}
}

func (service *NamedWebSocket_Service) serveConsoleTemplate(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "text/html; charset=utf-8")

	// Only allow console access from localhost
	if isRequestFromLocalHost := service.checkRequestIsFromLocalHost(r.Host); !isRequestFromLocalHost {
		http.Error(w, fmt.Sprintf("Named WebSockets Test Console is only accessible from the local machine (i.e http://localhost:%d/console)", service.Port), 403)
		return
	}

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	if r.URL.Path == "/" {
		fmt.Fprint(w, "
A Named WebSockets Proxy is running on this host
")
		return
	}

	if r.URL.Path != "/console" {
		http.Error(w, "Not found", 404)
		return
	}

	consoleHTML, err := Asset("_templates/console.html")
	if err != nil {
		// Asset was not found.
		http.Error(w, "Not found", 404)
		return
	}

	t := template.Must(template.New("console").Parse(string(consoleHTML)))
	if t == nil {
		http.Error(w, "Internal server error", 501)
		return
	}

	t.Execute(w, service.Port)
}

func (service *NamedWebSocket_Service) serveLocalWSCreator(w http.ResponseWriter, r *http.Request) {
	// Only allow access from localhost to all services
	if isRequestFromLocalHost := service.checkRequestIsFromLocalHost(r.Host); !isRequestFromLocalHost {
		http.Error(w, fmt.Sprintf("This interface is only accessible from the local machine on this port (%d)", service.Port), 403)
		return
	}

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	if isValidRequest := isValidLocalRequest.MatchString(r.URL.Path); !isValidRequest {
		http.Error(w, "Not found", 404)
		return
	}

	if isValidWSUpgradeRequest := strings.ToLower(r.Header.Get("Upgrade")); isValidWSUpgradeRequest != "websocket" {
		http.Error(w, "Bad request", 400)
		return
	}

	isControl := isControlServiceRequest.MatchString(r.URL.Path)

	isNetwork := isNetworkServiceRequest.MatchString(r.URL.Path)
	isNetworkControl := isNetworkControlServiceRequest.MatchString(r.URL.Path)

	pathParts := strings.Split(r.URL.Path, "/")

	peerIdStr := pathParts[len(pathParts)-1]
	serviceName := pathParts[len(pathParts)-2]

	// Remove trailing peerId from service path
	servicePath := fmt.Sprintf("%s", strings.Join(pathParts[0:len(pathParts)-1], "/"))

	// Remove leading "/control" from service path if this is a control request
	if isControl {
		servicePath = fmt.Sprintf("/%s", strings.Join(pathParts[2:len(pathParts)-1], "/"))
	}

	if isValid := isValidServiceName.MatchString(serviceName); !isValid {
		http.Error(w, "Not found", 404)
		return
	}

	var group *NamedWebSocket_Service_Group
	if isNetwork || isNetworkControl {
		group = service.networkSockets
	} else {
		group = service.localSockets
	}

	// Resolve websocket connection (also, split Local and Network types with the same name)
	sock := group.Services[servicePath]
	if sock == nil {
		sock = NewNamedWebSocket(service, serviceName, service.Port, isNetwork, isControl)
		group.Services[servicePath] = sock
	}

	peerId, _ := strconv.Atoi(peerIdStr)

	// Handle websocket connection
	if isControl {
		sock.serveControl(w, r, peerId)
	} else {
		sock.servePeer(w, r, peerId)
	}
}

func (service *NamedWebSocket_Service) serveProxyWSCreator(w http.ResponseWriter, r *http.Request) {

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	if isValidRequest := isValidBroadcastRequest.MatchString(r.URL.Path); !isValidRequest {
		http.Error(w, "Not found", 404)
		return
	}

	if isValidWSUpgradeRequest := strings.ToLower(r.Header.Get("Upgrade")); isValidWSUpgradeRequest != "websocket" {
		http.Error(w, "Bad request", 400)
		return
	}

	isNetwork := isNetworkServiceRequest.MatchString(r.URL.Path)

	var group *NamedWebSocket_Service_Group

	if !isNetwork {
		group = service.localSockets
	} else {
		group = service.networkSockets
	}

	pathParts := strings.Split(r.URL.Path, "/")

	peerIdStr := pathParts[len(pathParts)-1]
	serviceHash := pathParts[len(pathParts)-2]

	serviceScope := pathParts[len(pathParts)-3]

	// Resolve serviceHash to an active named websocket service name
	serviceBCryptHash, _ := base64.StdEncoding.DecodeString(serviceHash)
	serviceBCryptHashStr := string(serviceBCryptHash)

	for serviceName := range group.knownServiceNames {
		if bcrypt.Match(serviceName, serviceBCryptHashStr) {

			if serviceScope == "local" {
				// Only allow services to connect to *local* services access from localhost addresses
				if isRequestFromLocalHost := service.checkRequestIsFromLocalHost(r.Host); !isRequestFromLocalHost {
					http.Error(w, fmt.Sprintf("This interface is only accessible from the local machine on this port (%d)", service.Port), 403)
					return
				}
			}

			sock := group.Services[fmt.Sprintf("/%s/%s", serviceScope, serviceName)]
			if sock == nil {
				log.Printf("Could not find matching %s NamedWebSocket object for service %s", serviceScope, serviceName)
				continue
			}

			peerId, _ := strconv.Atoi(peerIdStr)
			sock.serveProxy(w, r, peerId)
			break
		}
	}
}

func (service *NamedWebSocket_Service) checkRequestIsFromLocalHost(host string) bool {
	allowedLocalHosts := map[string]bool{
		fmt.Sprintf("localhost:%d", service.Port):        true,
		fmt.Sprintf("127.0.0.1:%d", service.Port):        true,
		fmt.Sprintf("::1:%d", service.Port):              true,
		fmt.Sprintf("%s:%d", service.Host, service.Port): true,
	}

	if allowedLocalHosts[host] {
		return true
	}

	return false
}

/** Simple in-memory storage table for TLS-SRP usernames/passwords **/

type CredentialsStore map[string]string

func (cs CredentialsStore) Lookup(user string) (v, s []byte, grp tls.SRPGroup, err error) {
	grp = tls.SRPGroup4096

	p := cs[user]
	if p == "" {
		return nil, nil, grp, nil
	}

	v = tls.SRPVerifier(user, p, Salt, grp)
	return v, Salt, grp, nil
}
<commit_msg>Add more robust Named Web Socket Proxy start-up process<commit_after>package namedwebsockets

import (
	"encoding/base64"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"text/template"

	"github.com/richtr/bcrypt"
	tls "github.com/richtr/go-tls-srp"
)

var (
	// Regular expression matchers

	serviceNameRegexStr = "[A-Za-z0-9/\\+=\\*\\._-]{1,255}"

	peerIdRegexStr = "[0-9]{4,}"

	isValidLocalRequest = regexp.MustCompile(fmt.Sprintf("^((/control)?/(network|local)/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isValidBroadcastRequest = regexp.MustCompile(fmt.Sprintf("^(/(network|local)/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isNetworkServiceRequest = regexp.MustCompile(fmt.Sprintf("^((/control)?/network/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isControlServiceRequest = regexp.MustCompile(fmt.Sprintf("^(/control/(network|local)/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isNetworkControlServiceRequest = regexp.MustCompile(fmt.Sprintf("^(/control/network/%s/%s)$", serviceNameRegexStr, peerIdRegexStr))

	isValidServiceName = regexp.MustCompile(fmt.Sprintf("^%s$", serviceNameRegexStr))

	// TLS-SRP configuration components

	// We deliberately only use a weak salt because we don't persistently store TLS-SRP credential data
	Salt = []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}

	serviceTab = CredentialsStore(map[string]string{})
)

type NamedWebSocket_Service struct {
	Host string
	Port int

	ProxyPort int

	localSockets   *NamedWebSocket_Service_Group
	networkSockets *NamedWebSocket_Service_Group
}

type NamedWebSocket_Service_Group struct {
	// All Named WebSocket services (local or network) that this service manages
	Services map[string]*NamedWebSocket

	// Discovery related trackers for services advertised and registered
	knownServiceNames map[string]bool

	AdvertisedServiceHashes map[string]bool

	ResolvedServiceRecords   map[string]*NamedWebSocket_DNSRecord
	UnresolvedServiceRecords map[string]*NamedWebSocket_DNSRecord
}

func NewNamedWebSocketService(host string, port int) *NamedWebSocket_Service {
	if host == "" {
		hostname, err := os.Hostname()
		if err != nil {
			log.Printf("Could not determine device hostname: %v\n", err)
			return nil
		}
		host = hostname
	}

	if port <= 1024 || port >= 65534 {
		port = 9009
	}

	service := &NamedWebSocket_Service{
		Host: host,
		Port: port,

		ProxyPort: 0,

		localSockets:   NewNamedWebSocketServiceGroup(),
		networkSockets: NewNamedWebSocketServiceGroup(),
	}
	return service
}

func NewNamedWebSocketServiceGroup() *NamedWebSocket_Service_Group {
	return &NamedWebSocket_Service_Group{
		Services:                make(map[string]*NamedWebSocket),
		knownServiceNames:       make(map[string]bool),
		AdvertisedServiceHashes: make(map[string]bool),

		ResolvedServiceRecords:   make(map[string]*NamedWebSocket_DNSRecord),
		UnresolvedServiceRecords: make(map[string]*NamedWebSocket_DNSRecord),
	}
}

func (service *NamedWebSocket_Service) Start() {
	// Start TLS-SRP Named WebSocket (wss) server
	go service.StartProxyServer()

	// Start mDNS/DNS-SD discovery service (with 10 second network polling interval)
	go service.StartDiscoveryServers(10)

	// Start HTTP/WebSocket endpoint server (blocking call)
	service.StartHTTPServer(false)
}

func (service *NamedWebSocket_Service) StartHTTPServer(async bool) {
	// Create a new custom http server multiplexer
	serveMux := http.NewServeMux()

	// Serve the test console
	serveMux.HandleFunc("/", service.serveConsoleTemplate)

	// Serve websocket creation endpoints for localhost clients
	serveMux.HandleFunc("/local/", service.serveLocalWSCreator)
	serveMux.HandleFunc("/network/", service.serveLocalWSCreator)

	// Serve websocket control endpoint for localhost clients
	serveMux.HandleFunc("/control/", service.serveLocalWSCreator)

	// Listen and on loopback address + port
	listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", service.Port))
	if err != nil {
		log.Fatal("Could not serve proxy. ", err)
	}

	log.Printf("Serving Named Web Socket Service at ws://localhost:%d/", service.Port)

	if async {
		go http.Serve(listener, serveMux)
	} else {
		http.Serve(listener, serveMux)
	}
}

func (service *NamedWebSocket_Service) StartProxyServer() {
	// Create a new custom http server multiplexer
	serveMux := http.NewServeMux()

	// Serve secure websocket creation endpoints for network clients
	serveMux.HandleFunc("/local/", service.serveProxyWSCreator)
	serveMux.HandleFunc("/network/", service.serveProxyWSCreator)

	// Generate random server salt for use in TLS-SRP data storage
	var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	b := make([]rune, 32)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	srpSaltKey := string(b)

	tlsServerConfig := &tls.Config{
		SRPLookup:   serviceTab,
		SRPSaltKey:  srpSaltKey,
		SRPSaltSize: len(Salt),
	}

	// Listen on all addresses + port
	tlsSrpListener, err := tls.Listen("tcp", ":0", tlsServerConfig)
	if err != nil {
		log.Fatal(err)
	}

	// Obtain and store the port of the proxy endpoint
	_, port, err := net.SplitHostPort(tlsSrpListener.Addr().String())
	if err != nil {
		log.Fatal(err)
	}

	service.ProxyPort, _ = strconv.Atoi(port)

	log.Printf("Serving Named Web Socket Proxy at wss://%s:%d/", service.Host, service.ProxyPort)

	http.Serve(tlsSrpListener, serveMux)
}

func (service *NamedWebSocket_Service) StartDiscoveryServers(timeoutSeconds int) {
	networkDiscoveryServer := NewDiscoveryServer("network")

	defer networkDiscoveryServer.Shutdown()

	go func() {
		localDiscoveryServer := NewDiscoveryServer("local")

		defer localDiscoveryServer.Shutdown()

		for !localDiscoveryServer.closed {
			localDiscoveryServer.Browse(service, timeoutSeconds)
		}
	}()

	for !networkDiscoveryServer.closed {
		networkDiscoveryServer.Browse(service, timeoutSeconds)
	}
}

func (service *NamedWebSocket_Service) serveConsoleTemplate(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "text/html; charset=utf-8")

	// Only allow console access from localhost
	if isRequestFromLocalHost := service.checkRequestIsFromLocalHost(r.Host); !isRequestFromLocalHost {
		http.Error(w, fmt.Sprintf("Named WebSockets Test Console is only accessible from the local machine (i.e http://localhost:%d/console)", service.Port), 403)
		return
	}

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	if r.URL.Path == "/" {
		fmt.Fprint(w, "
A Named WebSockets Proxy is running on this host
")
		return
	}

	if r.URL.Path != "/console" {
		http.Error(w, "Not found", 404)
		return
	}

	consoleHTML, err := Asset("_templates/console.html")
	if err != nil {
		// Asset was not found.
		http.Error(w, "Not found", 404)
		return
	}

	t := template.Must(template.New("console").Parse(string(consoleHTML)))
	if t == nil {
		http.Error(w, "Internal server error", 501)
		return
	}

	t.Execute(w, service.Port)
}

func (service *NamedWebSocket_Service) serveLocalWSCreator(w http.ResponseWriter, r *http.Request) {
	// Only allow access from localhost to all services
	if isRequestFromLocalHost := service.checkRequestIsFromLocalHost(r.Host); !isRequestFromLocalHost {
		http.Error(w, fmt.Sprintf("This interface is only accessible from the local machine on this port (%d)", service.Port), 403)
		return
	}

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	if isValidRequest := isValidLocalRequest.MatchString(r.URL.Path); !isValidRequest {
		http.Error(w, "Not found", 404)
		return
	}

	if isValidWSUpgradeRequest := strings.ToLower(r.Header.Get("Upgrade")); isValidWSUpgradeRequest != "websocket" {
		http.Error(w, "Bad request", 400)
		return
	}

	isControl := isControlServiceRequest.MatchString(r.URL.Path)

	isNetwork := isNetworkServiceRequest.MatchString(r.URL.Path)
	isNetworkControl := isNetworkControlServiceRequest.MatchString(r.URL.Path)

	pathParts := strings.Split(r.URL.Path, "/")

	peerIdStr := pathParts[len(pathParts)-1]
	serviceName := pathParts[len(pathParts)-2]

	// Remove trailing peerId from service path
	servicePath := fmt.Sprintf("%s", strings.Join(pathParts[0:len(pathParts)-1], "/"))

	// Remove leading "/control" from service path if this is a control request
	if isControl {
		servicePath = fmt.Sprintf("/%s", strings.Join(pathParts[2:len(pathParts)-1], "/"))
	}

	if isValid := isValidServiceName.MatchString(serviceName); !isValid {
		http.Error(w, "Not found", 404)
		return
	}

	var group *NamedWebSocket_Service_Group
	if isNetwork || isNetworkControl {
		group = service.networkSockets
	} else {
		group = service.localSockets
	}

	// Resolve websocket connection (also, split Local and Network types with the same name)
	sock := group.Services[servicePath]
	if sock == nil {
		sock = NewNamedWebSocket(service, serviceName, service.Port, isNetwork, isControl)
		group.Services[servicePath] = sock
	}

	peerId, _ := strconv.Atoi(peerIdStr)

	// Handle websocket connection
	if isControl {
		sock.serveControl(w, r, peerId)
	} else {
		sock.servePeer(w, r, peerId)
	}
}

func (service *NamedWebSocket_Service) serveProxyWSCreator(w http.ResponseWriter, r *http.Request) {

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	if isValidRequest := isValidBroadcastRequest.MatchString(r.URL.Path); !isValidRequest {
		http.Error(w, "Not found", 404)
		return
	}

	if isValidWSUpgradeRequest := strings.ToLower(r.Header.Get("Upgrade")); isValidWSUpgradeRequest != "websocket" {
		http.Error(w, "Bad request", 400)
		return
	}

	isNetwork := isNetworkServiceRequest.MatchString(r.URL.Path)

	var group *NamedWebSocket_Service_Group

	if !isNetwork {
		group = service.localSockets
	} else {
		group = service.networkSockets
	}

	pathParts := strings.Split(r.URL.Path, "/")

	peerIdStr := pathParts[len(pathParts)-1]
	serviceHash := pathParts[len(pathParts)-2]

	serviceScope := pathParts[len(pathParts)-3]

	// Resolve serviceHash to an active named websocket service name
	serviceBCryptHash, _ := base64.StdEncoding.DecodeString(serviceHash)
	serviceBCryptHashStr := string(serviceBCryptHash)

	for serviceName := range group.knownServiceNames {
		if bcrypt.Match(serviceName, serviceBCryptHashStr) {

			if serviceScope == "local" {
				// Only allow services to connect to *local* services access from localhost addresses
				if isRequestFromLocalHost := service.checkRequestIsFromLocalHost(r.Host); !isRequestFromLocalHost {
					http.Error(w, fmt.Sprintf("This interface is only accessible from the local machine on this port (%d)", service.Port), 403)
					return
				}
			}

			sock := group.Services[fmt.Sprintf("/%s/%s", serviceScope, serviceName)]
			if sock == nil {
				log.Printf("Could not find matching %s NamedWebSocket object for service %s", serviceScope, serviceName)
				continue
			}

			peerId, _ := strconv.Atoi(peerIdStr)
			sock.serveProxy(w, r, peerId)
			break
		}
	}
}

func (service *NamedWebSocket_Service) checkRequestIsFromLocalHost(host string) bool {
	allowedLocalHosts := map[string]bool{
		fmt.Sprintf("localhost:%d", service.Port):        true,
		fmt.Sprintf("127.0.0.1:%d", service.Port):        true,
		fmt.Sprintf("::1:%d", service.Port):              true,
		fmt.Sprintf("%s:%d", service.Host, service.Port): true,
	}

	if allowedLocalHosts[host] {
		return true
	}

	return false
}

/** Simple in-memory storage table for TLS-SRP usernames/passwords **/

type CredentialsStore map[string]string

func (cs CredentialsStore) Lookup(user string) (v, s []byte, grp tls.SRPGroup, err error) {
	grp = tls.SRPGroup4096

	p := cs[user]
	if p == "" {
		return nil, nil, grp, nil
	}

	v = tls.SRPVerifier(user, p, Salt, grp)
	return v, Salt, grp, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/docker/docker/api/types/swarm"
	"golang.org/x/net/context"
)

// NoSuchService is the error returned when a given service does not exist.
type NoSuchService struct {
	ID  string
	Err error
}

func (err *NoSuchService) Error() string {
	if err.Err != nil {
		return err.Err.Error()
	}
	return "No such service: " + err.ID
}

// CreateServiceOptions specify parameters to the CreateService function.
//
// See https://goo.gl/KrVjHz for more details.
type CreateServiceOptions struct {
	Auth AuthConfiguration `qs:"-"`
	swarm.ServiceSpec
	Context context.Context
}

// CreateService creates a new service, returning the service instance
// or an error in case of failure.
//
// See https://goo.gl/KrVjHz for more details.
func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) {
	headers, err := headersWithAuth(opts.Auth)
	if err != nil {
		return nil, err
	}
	path := "/services/create?" + queryString(opts)
	resp, err := c.do("POST", path, doOptions{
		headers:   headers,
		data:      opts.ServiceSpec,
		forceJSON: true,
		context:   opts.Context,
	})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var service swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
		return nil, err
	}
	return &service, nil
}

// RemoveServiceOptions encapsulates options to remove a service.
//
// See https://goo.gl/Tqrtya for more details.
type RemoveServiceOptions struct {
	ID      string `qs:"-"`
	Context context.Context
}

// RemoveService removes a service, returning an error in case of failure.
//
// See https://goo.gl/Tqrtya for more details.
func (c *Client) RemoveService(opts RemoveServiceOptions) error {
	path := "/services/" + opts.ID
	resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return &NoSuchService{ID: opts.ID}
		}
		return err
	}
	resp.Body.Close()
	return nil
}

// UpdateServiceOptions specify parameters to the UpdateService function.
//
// See https://goo.gl/wu3MmS for more details.
type UpdateServiceOptions struct {
	Auth AuthConfiguration `qs:"-"`
	swarm.ServiceSpec
	Context context.Context
	Version uint64
}

// UpdateService updates the service at ID with the options
//
// See https://goo.gl/wu3MmS for more details.
func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error {
	headers, err := headersWithAuth(opts.Auth)
	if err != nil {
		return err
	}
	params := make(url.Values)
	params.Set("version", strconv.FormatUint(opts.Version, 10))
	resp, err := c.do("POST", "/services/"+id+"/update?"+params.Encode(), doOptions{
		headers:   headers,
		data:      opts.ServiceSpec,
		forceJSON: true,
		context:   opts.Context,
	})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return &NoSuchService{ID: id}
		}
		return err
	}
	defer resp.Body.Close()
	return nil
}

// InspectService returns information about a service by its ID.
//
// See https://goo.gl/dHmr75 for more details.
func (c *Client) InspectService(id string) (*swarm.Service, error) {
	path := "/services/" + id
	resp, err := c.do("GET", path, doOptions{})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return nil, &NoSuchService{ID: id}
		}
		return nil, err
	}
	defer resp.Body.Close()
	var service swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
		return nil, err
	}
	return &service, nil
}

// ListServicesOptions specify parameters to the ListServices function.
//
// See https://goo.gl/DwvNMd for more details.
type ListServicesOptions struct {
	Filters map[string][]string
	Context context.Context
}

// ListServices returns a slice of services matching the given criteria.
//
// See https://goo.gl/DwvNMd for more details.
func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) {
	path := "/services?" + queryString(opts)
	resp, err := c.do("GET", path, doOptions{context: opts.Context})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var services []swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&services); err != nil {
		return nil, err
	}
	return services, nil
}

// LogsServiceOptions represents the set of options used when getting logs from a
// service.
type LogsServiceOptions struct {
	Context           context.Context
	Service           string        `qs:"-"`
	OutputStream      io.Writer     `qs:"-"`
	ErrorStream       io.Writer     `qs:"-"`
	InactivityTimeout time.Duration `qs:"-"`
	Tail              string

	// Use raw terminal? Usually true when the container contains a TTY.
	RawTerminal bool `qs:"-"`
	Since       int64
	Follow      bool
	Stdout      bool
	Stderr      bool
	Timestamps  bool
	Details     bool
}

// LogsService gets stdout and stderr logs from the specified service.
//
// When LogsServiceOptions.RawTerminal is set to false, go-dockerclient will multiplex
// the streams and send the containers stdout to LogsServiceOptions.OutputStream, and
// stderr to LogsServiceOptions.ErrorStream.
//
// When LogsServiceOptions.RawTerminal is true, callers will get the raw stream on
// LogsServiceOptions.OutputStream.
func (c *Client) LogsService(opts LogsServiceOptions) error {
	if opts.Service == "" {
		return &NoSuchService{ID: opts.Service}
	}
	if opts.Tail == "" {
		opts.Tail = "all"
	}
	path := "/services/" + opts.Service + "/logs?" + queryString(opts)
	return c.stream("GET", path, streamOptions{
		setRawTerminal:    opts.RawTerminal,
		stdout:            opts.OutputStream,
		stderr:            opts.ErrorStream,
		inactivityTimeout: opts.InactivityTimeout,
		context:           opts.Context,
	})
}
<commit_msg>fix method name<commit_after>// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/docker/docker/api/types/swarm"
	"golang.org/x/net/context"
)

// NoSuchService is the error returned when a given service does not exist.
type NoSuchService struct {
	ID  string
	Err error
}

func (err *NoSuchService) Error() string {
	if err.Err != nil {
		return err.Err.Error()
	}
	return "No such service: " + err.ID
}

// CreateServiceOptions specify parameters to the CreateService function.
//
// See https://goo.gl/KrVjHz for more details.
type CreateServiceOptions struct {
	Auth AuthConfiguration `qs:"-"`
	swarm.ServiceSpec
	Context context.Context
}

// CreateService creates a new service, returning the service instance
// or an error in case of failure.
//
// See https://goo.gl/KrVjHz for more details.
func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) {
	headers, err := headersWithAuth(opts.Auth)
	if err != nil {
		return nil, err
	}
	path := "/services/create?" + queryString(opts)
	resp, err := c.do("POST", path, doOptions{
		headers:   headers,
		data:      opts.ServiceSpec,
		forceJSON: true,
		context:   opts.Context,
	})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var service swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
		return nil, err
	}
	return &service, nil
}

// RemoveServiceOptions encapsulates options to remove a service.
//
// See https://goo.gl/Tqrtya for more details.
type RemoveServiceOptions struct {
	ID      string `qs:"-"`
	Context context.Context
}

// RemoveService removes a service, returning an error in case of failure.
//
// See https://goo.gl/Tqrtya for more details.
func (c *Client) RemoveService(opts RemoveServiceOptions) error {
	path := "/services/" + opts.ID
	resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return &NoSuchService{ID: opts.ID}
		}
		return err
	}
	resp.Body.Close()
	return nil
}

// UpdateServiceOptions specify parameters to the UpdateService function.
//
// See https://goo.gl/wu3MmS for more details.
type UpdateServiceOptions struct {
	Auth AuthConfiguration `qs:"-"`
	swarm.ServiceSpec
	Context context.Context
	Version uint64
}

// UpdateService updates the service at ID with the options
//
// See https://goo.gl/wu3MmS for more details.
func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error {
	headers, err := headersWithAuth(opts.Auth)
	if err != nil {
		return err
	}
	params := make(url.Values)
	params.Set("version", strconv.FormatUint(opts.Version, 10))
	resp, err := c.do("POST", "/services/"+id+"/update?"+params.Encode(), doOptions{
		headers:   headers,
		data:      opts.ServiceSpec,
		forceJSON: true,
		context:   opts.Context,
	})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return &NoSuchService{ID: id}
		}
		return err
	}
	defer resp.Body.Close()
	return nil
}

// InspectService returns information about a service by its ID.
//
// See https://goo.gl/dHmr75 for more details.
func (c *Client) InspectService(id string) (*swarm.Service, error) {
	path := "/services/" + id
	resp, err := c.do("GET", path, doOptions{})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return nil, &NoSuchService{ID: id}
		}
		return nil, err
	}
	defer resp.Body.Close()
	var service swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
		return nil, err
	}
	return &service, nil
}

// ListServicesOptions specify parameters to the ListServices function.
//
// See https://goo.gl/DwvNMd for more details.
type ListServicesOptions struct {
	Filters map[string][]string
	Context context.Context
}

// ListServices returns a slice of services matching the given criteria.
//
// See https://goo.gl/DwvNMd for more details.
func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) {
	path := "/services?" + queryString(opts)
	resp, err := c.do("GET", path, doOptions{context: opts.Context})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var services []swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&services); err != nil {
		return nil, err
	}
	return services, nil
}

// LogsServiceOptions represents the set of options used when getting logs from a
// service.
type LogsServiceOptions struct {
	Context           context.Context
	Service           string        `qs:"-"`
	OutputStream      io.Writer     `qs:"-"`
	ErrorStream       io.Writer     `qs:"-"`
	InactivityTimeout time.Duration `qs:"-"`
	Tail              string

	// Use raw terminal? Usually true when the container contains a TTY.
	RawTerminal bool `qs:"-"`
	Since       int64
	Follow      bool
	Stdout      bool
	Stderr      bool
	Timestamps  bool
	Details     bool
}

// GetServiceLogs gets stdout and stderr logs from the specified service.
//
// When LogsServiceOptions.RawTerminal is set to false, go-dockerclient will multiplex
// the streams and send the containers stdout to LogsServiceOptions.OutputStream, and
// stderr to LogsServiceOptions.ErrorStream.
//
// When LogsServiceOptions.RawTerminal is true, callers will get the raw stream on
// LogsServiceOptions.OutputStream.
func (c *Client) GetServiceLogs(opts LogsServiceOptions) error {
	if opts.Service == "" {
		return &NoSuchService{ID: opts.Service}
	}
	if opts.Tail == "" {
		opts.Tail = "all"
	}
	path := "/services/" + opts.Service + "/logs?" + queryString(opts)
	return c.stream("GET", path, streamOptions{
		setRawTerminal:    opts.RawTerminal,
		stdout:            opts.OutputStream,
		stderr:            opts.ErrorStream,
		inactivityTimeout: opts.InactivityTimeout,
		context:           opts.Context,
	})
}
<|endoftext|>


<commit_before>// Copyright 2016 Google 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 render

import (
	"bytes"
	"fmt"
	"io"
	"path"
	"sort"
	"strconv"
	"strings"

	"github.com/googlecodelabs/tools/claat/types"
)

// MD renders nodes as markdown for the target env.
func MD(env string, nodes ...types.Node) (string, error) {
	var buf bytes.Buffer
	if err := WriteMD(&buf, env, nodes...); err != nil {
		return "", err
	}
	return buf.String(), nil
}

// WriteMD does the same as MD but outputs rendered markup to w.
func WriteMD(w io.Writer, env string, nodes ...types.Node) error {
	mw := mdWriter{w: w, env: env}
	return mw.write(nodes...)
}

type mdWriter struct {
	w         io.Writer // output writer
	env       string    // target environment
	err       error     // error during any writeXxx methods
	lineStart bool
}

func (mw *mdWriter) writeBytes(b []byte) {
	if mw.err != nil {
		return
	}
	mw.lineStart = len(b) > 0 && b[len(b)-1] == '\n'
	_, mw.err = mw.w.Write(b)
}

func (mw *mdWriter) writeString(s string) {
	mw.writeBytes([]byte(s))
}

func (mw *mdWriter) space() {
	if !mw.lineStart {
		mw.writeString(" ")
	}
}

func (mw *mdWriter) newBlock() {
	if !mw.lineStart {
		mw.writeBytes(newLine)
	}
	mw.writeBytes(newLine)
}

func (mw *mdWriter) matchEnv(v []string) bool {
	if len(v) == 0 || mw.env == "" {
		return true
	}
	i := sort.SearchStrings(v, mw.env)
	return i < len(v) && v[i] == mw.env
}

func (mw *mdWriter) write(nodes ...types.Node) error {
	for _, n := range nodes {
		if !mw.matchEnv(n.Env()) {
			continue
		}
		switch n := n.(type) {
		case *types.TextNode:
			mw.text(n)
		case *types.ImageNode:
			mw.image(n)
		case *types.URLNode:
			mw.url(n)
		case *types.ButtonNode:
			mw.write(n.Content.Nodes...)
		case *types.CodeNode:
			mw.code(n)
		case *types.ListNode:
			mw.list(n)
		case *types.ImportNode:
			if len(n.Content.Nodes) == 0 {
				break
			}
			mw.write(n.Content.Nodes...)
		case *types.ItemsListNode:
			mw.itemsList(n)
		//case *types.GridNode:
		//	mw.grid(n)
		case *types.InfoboxNode:
			mw.infobox(n)
		//case *types.SurveyNode:
		//	mw.survey(n)
		case *types.HeaderNode:
			mw.header(n)
		case *types.YouTubeNode:
			mw.youtube(n)
		}
		if mw.err != nil {
			return mw.err
		}
	}
	return nil
}

func (mw *mdWriter) text(n *types.TextNode) {
	if n.Bold {
		mw.writeString("**")
	}
	if n.Italic {
		mw.writeString(" *")
	}
	if n.Code {
		mw.writeString("`")
	}
	mw.writeString(n.Value)
	if n.Code {
		mw.writeString("`")
	}
	if n.Italic {
		mw.writeString("* ")
	}
	if n.Bold {
		mw.writeString("**")
	}
}

func (mw *mdWriter) image(n *types.ImageNode) {
	mw.space()
	mw.writeString(" 0 {
		mw.writeString(fmt.Sprintf(" width=\"%.2f\" ", n.Width))
	}

	mw.writeString("/>")
}

func (mw *mdWriter) url(n *types.URLNode) {
	mw.space()
	if n.URL != "" {
		mw.writeString("[")
	}
	for _, cn := range n.Content.Nodes {
		if t, ok := cn.(*types.TextNode); ok {
			mw.writeString(t.Value)
		}
	}
	if n.URL != "" {
		mw.writeString("](")
		mw.writeString(n.URL)
		mw.writeString(")")
	}
}

func (mw *mdWriter) code(n *types.CodeNode) {
	mw.newBlock()
	defer mw.writeBytes(newLine)
	if n.Term {
		var buf bytes.Buffer
		const prefix = "    "
		lineStart := true
		for _, r := range n.Value {
			if lineStart {
				buf.WriteString(prefix)
			}
			buf.WriteRune(r)
			lineStart = r == '\n'
		}
		mw.writeBytes(buf.Bytes())
		return
	}
	mw.writeString("```")
	mw.writeString(n.Lang)
	mw.writeBytes(newLine)
	mw.writeString(n.Value)
	if !mw.lineStart {
		mw.writeBytes(newLine)
	}
	mw.writeString("```")
}

func (mw *mdWriter) list(n *types.ListNode) {
	if n.Block() == true {
		mw.newBlock()
	}
	mw.write(n.Nodes...)
	if !mw.lineStart {
		mw.writeBytes(newLine)
	}
}

func (mw *mdWriter) itemsList(n *types.ItemsListNode) {
	mw.newBlock()
	for i, item := range n.Items {
		s := "* "
		if n.Type() == types.NodeItemsList && n.Start > 0 {
			s = strconv.Itoa(i+n.Start) + ". "
		}
		mw.writeString(s)
		mw.write(item.Nodes...)
		if !mw.lineStart {
			mw.writeBytes(newLine)
		}
	}
}

func (mw *mdWriter) infobox(n *types.InfoboxNode) {
	// TODO: This should use the "detail item" syntax so that it can be pure MD and not HTML
	// kind
	// : <content>
	//
	// The main issue is that when you do write(n.Content.Nodes...) it always adds two newlines
	// at the beginning.
	mw.newBlock()
	mw.writeString(`<aside class="`)
	mw.writeString(string(n.Kind))
	mw.writeString(`">`)
	mw.write(n.Content.Nodes...)
	mw.writeString("")
}

func (mw *mdWriter) header(n *types.HeaderNode) {
	mw.newBlock()
	mw.writeString(strings.Repeat("#", n.Level+1))
	mw.writeString(" ")
	mw.write(n.Content.Nodes...)
	if !mw.lineStart {
		mw.writeBytes(newLine)
	}
}

func (mw *mdWriter) youtube(n *types.YouTubeNode) {
	mw.newBlock()
	mw.writeString(fmt.Sprintf(`<video id="%s"></video>`, n.VideoID))
}
<commit_msg>Fix gdoc to md infobox syntax.<commit_after>// Copyright 2016 Google 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 render

import (
	"bytes"
	"fmt"
	"io"
	"path"
	"sort"
	"strconv"
	"strings"

	"github.com/googlecodelabs/tools/claat/types"
)

// MD renders nodes as markdown for the target env.
func MD(env string, nodes ...types.Node) (string, error) {
	var buf bytes.Buffer
	if err := WriteMD(&buf, env, nodes...); err != nil {
		return "", err
	}
	return buf.String(), nil
}

// WriteMD does the same as MD but outputs rendered markup to w.
func WriteMD(w io.Writer, env string, nodes ...types.Node) error {
	mw := mdWriter{w: w, env: env}
	return mw.write(nodes...)
}

type mdWriter struct {
	w         io.Writer // output writer
	env       string    // target environment
	err       error     // error during any writeXxx methods
	lineStart bool
}

func (mw *mdWriter) writeBytes(b []byte) {
	if mw.err != nil {
		return
	}
	mw.lineStart = len(b) > 0 && b[len(b)-1] == '\n'
	_, mw.err = mw.w.Write(b)
}

func (mw *mdWriter) writeString(s string) {
	mw.writeBytes([]byte(s))
}

func (mw *mdWriter) space() {
	if !mw.lineStart {
		mw.writeString(" ")
	}
}

func (mw *mdWriter) newBlock() {
	if !mw.lineStart {
		mw.writeBytes(newLine)
	}
	mw.writeBytes(newLine)
}

func (mw *mdWriter) matchEnv(v []string) bool {
	if len(v) == 0 || mw.env == "" {
		return true
	}
	i := sort.SearchStrings(v, mw.env)
	return i < len(v) && v[i] == mw.env
}

func (mw *mdWriter) write(nodes ...types.Node) error {
	for _, n := range nodes {
		if !mw.matchEnv(n.Env()) {
			continue
		}
		switch n := n.(type) {
		case *types.TextNode:
			mw.text(n)
		case *types.ImageNode:
			mw.image(n)
		case *types.URLNode:
			mw.url(n)
		case *types.ButtonNode:
			mw.write(n.Content.Nodes...)
		case *types.CodeNode:
			mw.code(n)
		case *types.ListNode:
			mw.list(n)
		case *types.ImportNode:
			if len(n.Content.Nodes) == 0 {
				break
			}
			mw.write(n.Content.Nodes...)
		case *types.ItemsListNode:
			mw.itemsList(n)
		//case *types.GridNode:
		//	mw.grid(n)
		case *types.InfoboxNode:
			mw.infobox(n)
		//case *types.SurveyNode:
		//	mw.survey(n)
		case *types.HeaderNode:
			mw.header(n)
		case *types.YouTubeNode:
			mw.youtube(n)
		}
		if mw.err != nil {
			return mw.err
		}
	}
	return nil
}

func (mw *mdWriter) text(n *types.TextNode) {
	if n.Bold {
		mw.writeString("**")
	}
	if n.Italic {
		mw.writeString(" *")
	}
	if n.Code {
		mw.writeString("`")
	}
	mw.writeString(n.Value)
	if n.Code {
		mw.writeString("`")
	}
	if n.Italic {
		mw.writeString("* ")
	}
	if n.Bold {
		mw.writeString("**")
	}
}

func (mw *mdWriter) image(n *types.ImageNode) {
	mw.space()
	mw.writeString(" 0 {
		mw.writeString(fmt.Sprintf(" width=\"%.2f\" ", n.Width))
	}

	mw.writeString("/>")
}

func (mw *mdWriter) url(n *types.URLNode) {
	mw.space()
	if n.URL != "" {
		mw.writeString("[")
	}
	for _, cn := range n.Content.Nodes {
		if t, ok := cn.(*types.TextNode); ok {
			mw.writeString(t.Value)
		}
	}
	if n.URL != "" {
		mw.writeString("](")
		mw.writeString(n.URL)
		mw.writeString(")")
	}
}

func (mw *mdWriter) code(n *types.CodeNode) {
	mw.newBlock()
	defer mw.writeBytes(newLine)
	if n.Term {
		var buf bytes.Buffer
		const prefix = "    "
		lineStart := true
		for _, r := range n.Value {
			if lineStart {
				buf.WriteString(prefix)
			}
			buf.WriteRune(r)
			lineStart = r == '\n'
		}
		mw.writeBytes(buf.Bytes())
		return
	}
	mw.writeString("```")
	mw.writeString(n.Lang)
	mw.writeBytes(newLine)
	mw.writeString(n.Value)
	if !mw.lineStart {
		mw.writeBytes(newLine)
	}
	mw.writeString("```")
}

func (mw *mdWriter) list(n *types.ListNode) {
	if n.Block() == true {
		mw.newBlock()
	}
	mw.write(n.Nodes...)
	if !mw.lineStart {
		mw.writeBytes(newLine)
	}
}

func (mw *mdWriter) itemsList(n *types.ItemsListNode) {
	mw.newBlock()
	for i, item := range n.Items {
		s := "* "
		if n.Type() == types.NodeItemsList && n.Start > 0 {
			s = strconv.Itoa(i+n.Start) + ". "
		}
		mw.writeString(s)
		mw.write(item.Nodes...)
		if !mw.lineStart {
			mw.writeBytes(newLine)
		}
	}
}

func (mw *mdWriter) infobox(n *types.InfoboxNode) {
	// TODO: This should use the "detail item" syntax so that it can be pure MD and not HTML
	// kind
	// : <content>
	//
	// The main issue is that when you do write(n.Content.Nodes...) it always adds two newlines
	// at the beginning. This is because the gdoc parser parses info boxes as a list node
	// followed by a text node, and the list node is an md block unto itself, resulting in two
	// newlines being added to the output before the text.
	mw.newBlock()
	mw.writeString(`<aside class="`)
	class := "positive"
	if n.Kind == types.InfoboxNegative {
		class = "negative"
	}
	mw.writeString(class)
	mw.writeString(`">`)
	mw.write(n.Content.Nodes...)
	mw.writeString("")
}

func (mw *mdWriter) header(n *types.HeaderNode) {
	mw.newBlock()
	mw.writeString(strings.Repeat("#", n.Level+1))
	mw.writeString(" ")
	mw.write(n.Content.Nodes...)
	if !mw.lineStart {
		mw.writeBytes(newLine)
	}
}

func (mw *mdWriter) youtube(n *types.YouTubeNode) {
	mw.newBlock()
	mw.writeString(fmt.Sprintf(`<video id="%s"></video>`, n.VideoID))
}
<|endoftext|>


<commit_before>package firebaseJwtValidator

import (
	"encoding/base64"
	"encoding/json"
	"log"
	"time"
)

const issuerPrefix = "https://securetoken.google.com/"

type claims struct {
	Aud, Iss, Sub string
	Exp, Iat      int64
}

func decodeRawClaims(raw string) (bool, claims) {

	jsonStr, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		log.Printf("Unable to validate claims due to input not being Base64 %v", raw)
		return false, claims{}
	}

	var c claims
	err = json.Unmarshal(jsonStr, &c)
	if err != nil {
		log.Printf("Unable to validate claims due to input not being valid json %v", string(jsonStr))
		return false, claims{}
	}
	return true, c
}

type ClaimsValidator struct {
}

func (hv *ClaimsValidator) Validate(raw string, params ValidatorParams) bool {
	success, c := decodeRawClaims(raw)
	if !success {
		return false
	}

	if c.Sub == "" {
		return false
	}

	now := time.Now().Unix()
	if c.Iat >= now {
		log.Printf("Unable to validate claims as they are issued in the future %v", c)
		return false
	}

	if c.Exp < now {
		log.Printf("Unable to validate claims as they are expired %v", c)
		return false
	}

	if c.Iss != issuerPrefix+params.ProjectID {
		log.Printf("Unable to validate claims due to invalid issuer %v", c)
		return false
	}

	if c.Aud != params.ProjectID {
		log.Printf("Unable to validate claims due to invalid audience %v", c)
		return false
	}

	return true
}
<commit_msg>Fixed a bug where a token that was issued at the same second as the validation would happen would be rejected<commit_after>package firebaseJwtValidator

import (
	"encoding/base64"
	"encoding/json"
	"log"
	"time"
)

const issuerPrefix = "https://securetoken.google.com/"

type claims struct {
	Aud, Iss, Sub string
	Exp, Iat      int64
}

func decodeRawClaims(raw string) (bool, claims) {

	jsonStr, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		log.Printf("Unable to validate claims due to input not being Base64 %v", raw)
		return false, claims{}
	}

	var c claims
	err = json.Unmarshal(jsonStr, &c)
	if err != nil {
		log.Printf("Unable to validate claims due to input not being valid json %v", string(jsonStr))
		return false, claims{}
	}
	return true, c
}

type ClaimsValidator struct {
}

func (hv *ClaimsValidator) Validate(raw string, params ValidatorParams) bool {
	success, c := decodeRawClaims(raw)
	if !success {
		return false
	}

	if c.Sub == "" {
		return false
	}

	now := time.Now().Unix()
	if c.Iat > now {
		log.Printf("Unable to validate claims as they are issued in the future %v", c)
		return false
	}

	if c.Exp < now {
		log.Printf("Unable to validate claims as they are expired %v", c)
		return false
	}

	if c.Iss != issuerPrefix+params.ProjectID {
		log.Printf("Unable to validate claims due to invalid issuer %v", c)
		return false
	}

	if c.Aud != params.ProjectID {
		log.Printf("Unable to validate claims due to invalid audience %v", c)
		return false
	}

	return true
}
<|endoftext|>


<commit_before>package roaring

type RoaringBitmap struct {
	highlowcontainer roaringArray
}

func NewRoaringBitmap() *RoaringBitmap {
	return &RoaringBitmap{*newRoaringArray()}
}

func (self *RoaringBitmap) Clear() {
	self.highlowcontainer = *newRoaringArray()
}

func (self *RoaringBitmap) ToArray() []int {
	array := make([]int, self.GetCardinality())
	pos := 0
	pos2 := 0

	for pos < self.highlowcontainer.size() {
		hs := toIntUnsigned(self.highlowcontainer.getKeyAtIndex(pos)) << 16
		c := self.highlowcontainer.getContainerAtIndex(pos)
		pos++
		c.fillLeastSignificant16bits(array, pos2, hs)
		pos2 += c.getCardinality()
	}
	return array
}
func (self *RoaringBitmap) Clone() *RoaringBitmap {
	ptr := new(RoaringBitmap)
	ptr.highlowcontainer = *self.highlowcontainer.clone()
	return ptr
}

func (self *RoaringBitmap) Contains(x int) bool {
	hb := highbits(x)
	c := self.highlowcontainer.getContainer(hb)
	return c != nil && c.contains(lowbits(x))

}

func (self *RoaringBitmap) Equals(o interface{}) bool {
	srb := o.(*RoaringBitmap)
	if srb != nil {
		return srb.highlowcontainer.equals(self.highlowcontainer)
	}
	return false
}

func (self *RoaringBitmap) Add(x int) {
	hb := highbits(x)
	i := self.highlowcontainer.getIndex(hb)
	if i >= 0 {
		self.highlowcontainer.setContainerAtIndex(i, self.highlowcontainer.getContainerAtIndex(i).add(lowbits(x)))
	} else {
		newac := newArrayContainer()
		self.highlowcontainer.insertNewKeyValueAt(-i-1, hb, newac.add(lowbits(x)))
	}
}

func (self *RoaringBitmap) GetCardinality() int {
	size := 0
	for i := 0; i < self.highlowcontainer.size(); i++ {
		size += self.highlowcontainer.getContainerAtIndex(i).getCardinality()
	}
	return size
}

func (self *RoaringBitmap) And(x2 *RoaringBitmap) *RoaringBitmap {
	results := And(self, x2)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func (self *RoaringBitmap) Xor(x2 *RoaringBitmap) *RoaringBitmap {
	results := Xor(self, x2)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func (self *RoaringBitmap) Or(x2 *RoaringBitmap) *RoaringBitmap {
	results := Or(self, x2)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func (self *RoaringBitmap) AndNot(x2 *RoaringBitmap) *RoaringBitmap {
	results := AndNot(self, x2)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func Or(x1, x2 *RoaringBitmap) *RoaringBitmap {
	answer := NewRoaringBitmap()
	pos1 := 0
	pos2 := 0
	length1 := x1.highlowcontainer.size()
	length2 := x2.highlowcontainer.size()
main:
	for {
		if (pos1 < length1) && (pos2 < length2) {
			s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
			s2 := x2.highlowcontainer.getKeyAtIndex(pos2)

			for {
				if s1 < s2 {
					answer.highlowcontainer.appendCopy(x1.highlowcontainer, pos1)
					pos1++
					if pos1 == length1 {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
				} else if s1 > s2 {
					answer.highlowcontainer.appendCopy(x2.highlowcontainer, pos2)
					pos2++
					if pos2 == length2 {
						break main
					}
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				} else {

					answer.highlowcontainer.append(s1, x1.highlowcontainer.getContainerAtIndex(pos1).or(x2.highlowcontainer.getContainerAtIndex(pos2)))
					pos1++
					pos2++
					if (pos1 == length1) || (pos2 == length2) {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				}
			}
		} else {
			break
		}
	}
	if pos1 == length1 {
		answer.highlowcontainer.appendCopyMany(x2.highlowcontainer, pos2, length2)
	} else if pos2 == length2 {
		answer.highlowcontainer.appendCopyMany(x1.highlowcontainer, pos1, length1)
	}
	return answer
}

func And(x1, x2 *RoaringBitmap) *RoaringBitmap {
	answer := NewRoaringBitmap()
	pos1 := 0
	pos2 := 0
	length1 := x1.highlowcontainer.size()
	length2 := x2.highlowcontainer.size()
main:
	for {
		if pos1 < length1 && pos2 < length2 {
			s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
			s2 := x2.highlowcontainer.getKeyAtIndex(pos2)
			for {
				if s1 < s2 {
					pos1++
					if pos1 == length1 {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
				} else if s1 > s2 {
					pos2++
					if pos2 == length2 {
						break main
					}
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				} else {
					C := x1.highlowcontainer.getContainerAtIndex(pos1)
					C = C.and(x2.highlowcontainer.getContainerAtIndex(pos2))

					if C.getCardinality() > 0 {
						answer.highlowcontainer.append(s1, C)
					}
					pos1++
					pos2++
					if (pos1 == length1) || (pos2 == length2) {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)

				}
			}
		} else {
			break
		}
	}
	return answer
}
func Xor(x1, x2 *RoaringBitmap) *RoaringBitmap {
	answer := NewRoaringBitmap()
	pos1 := 0
	pos2 := 0
	length1 := x1.highlowcontainer.size()
	length2 := x2.highlowcontainer.size()

main:
	for {
		if (pos1 < length1) && (pos2 < length2) {
			s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
			s2 := x2.highlowcontainer.getKeyAtIndex(pos2)
			if s1 < s2 {
				answer.highlowcontainer.appendCopy(x1.highlowcontainer, pos1)

				pos1++
				if pos1 == length1 {
					break main
				}
				s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
			} else if s1 > s2 {
				answer.highlowcontainer.appendCopy(x2.highlowcontainer, pos2)
				pos2++
				if pos2 == length2 {
					break main
				}
				s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
			} else {
				c := x1.highlowcontainer.getContainerAtIndex(pos1).xor(x2.highlowcontainer.getContainerAtIndex(pos2))
				if c.getCardinality() > 0 {
					answer.highlowcontainer.append(s1, c)
				}
				pos1++
				pos2++
				if (pos1 == length1) || (pos2 == length2) {
					break main
				}
				s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
				s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
			}
		} else {
			break
		}
	}
	if pos1 == length1 {
		answer.highlowcontainer.appendCopyMany(x2.highlowcontainer, pos2, length2)
	} else if pos2 == length2 {
		answer.highlowcontainer.appendCopyMany(x1.highlowcontainer, pos1, length1)
	}
	return answer
}

func AndNot(x1, x2 *RoaringBitmap) *RoaringBitmap {
	answer := NewRoaringBitmap()
	pos1 := 0
	pos2 := 0
	length1 := x1.highlowcontainer.size()
	length2 := x2.highlowcontainer.size()

main:
	for {
		if pos1 < length1 && pos2 < length2 {
			s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
			s2 := x2.highlowcontainer.getKeyAtIndex(pos2)
			for {
				if s1 < s2 {
					answer.highlowcontainer.appendCopy(x1.highlowcontainer, pos1)
					pos1++
					if pos1 == length1 {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
				} else if s1 > s2 {
					pos2++
					if pos2 == length2 {
						break main
					}
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				} else {
					C := x1.highlowcontainer.getContainerAtIndex(pos1)
					C.andNot(x2.highlowcontainer.getContainerAtIndex(pos2))
					if C.getCardinality() > 0 {
						answer.highlowcontainer.append(s1, C)
					}
					pos1++
					pos2++
					if (pos1 == length1) || (pos2 == length2) {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				}
			}
		} else {
			break
		}
	}
	if pos2 == length2 {
		answer.highlowcontainer.appendCopyMany(x1.highlowcontainer, pos1, length1)
	}
	return answer
}

func BitmapOf(dat ...int) *RoaringBitmap {
	ans := NewRoaringBitmap()
	for _, i := range dat {
		ans.Add(i)
	}
	return ans
}

func (self *RoaringBitmap) Flip(rangeStart, rangeEnd int) *RoaringBitmap {
	results := Flip(self, rangeStart, rangeEnd)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func Flip(bm *RoaringBitmap, rangeStart, rangeEnd int) *RoaringBitmap {
	if rangeStart >= rangeEnd {
		return bm.Clone()
	}

	answer := NewRoaringBitmap()
	hbStart := highbits(rangeStart)
	lbStart := lowbits(rangeStart)
	hbLast := highbits(rangeEnd - 1)
	lbLast := lowbits(rangeEnd - 1)

	// copy the containers before the active area
	answer.highlowcontainer.appendCopiesUntil(bm.highlowcontainer, hbStart)

	max := toIntUnsigned(maxLowBit())
	for hb := hbStart; hb <= hbLast; hb++ {
		containerStart := 0
		if hb == hbStart {
			containerStart = toIntUnsigned(lbStart)
		}
		containerLast := max
		if hb == hbLast {
			containerLast = toIntUnsigned(lbLast)
		}

		i := bm.highlowcontainer.getIndex(hb)
		j := answer.highlowcontainer.getIndex(hb)

		if i >= 0 {
			c := bm.highlowcontainer.getContainerAtIndex(i).not(containerStart, containerLast)
			if c.getCardinality() > 0 {
				answer.highlowcontainer.insertNewKeyValueAt(-j-1, hb, c)
			}

		} else { // *think* the range of ones must never be
			// empty.
			answer.highlowcontainer.insertNewKeyValueAt(-j-1, hb,
				rangeOfOnes(containerStart, containerLast))
		}
	}
	// copy the containers after the active area.
	answer.highlowcontainer.appendCopiesAfter(bm.highlowcontainer, hbLast)

	return answer
}

<commit_msg>Adding package documentation<commit_after>// roaring is an implementation of Roaring Bitmaps in Go. See http://roaringbitmap.org for details.
package roaring

type RoaringBitmap struct {
	highlowcontainer roaringArray
}

func NewRoaringBitmap() *RoaringBitmap {
	return &RoaringBitmap{*newRoaringArray()}
}

func (self *RoaringBitmap) Clear() {
	self.highlowcontainer = *newRoaringArray()
}

func (self *RoaringBitmap) ToArray() []int {
	array := make([]int, self.GetCardinality())
	pos := 0
	pos2 := 0

	for pos < self.highlowcontainer.size() {
		hs := toIntUnsigned(self.highlowcontainer.getKeyAtIndex(pos)) << 16
		c := self.highlowcontainer.getContainerAtIndex(pos)
		pos++
		c.fillLeastSignificant16bits(array, pos2, hs)
		pos2 += c.getCardinality()
	}
	return array
}
func (self *RoaringBitmap) Clone() *RoaringBitmap {
	ptr := new(RoaringBitmap)
	ptr.highlowcontainer = *self.highlowcontainer.clone()
	return ptr
}

func (self *RoaringBitmap) Contains(x int) bool {
	hb := highbits(x)
	c := self.highlowcontainer.getContainer(hb)
	return c != nil && c.contains(lowbits(x))

}

func (self *RoaringBitmap) Equals(o interface{}) bool {
	srb := o.(*RoaringBitmap)
	if srb != nil {
		return srb.highlowcontainer.equals(self.highlowcontainer)
	}
	return false
}

func (self *RoaringBitmap) Add(x int) {
	hb := highbits(x)
	i := self.highlowcontainer.getIndex(hb)
	if i >= 0 {
		self.highlowcontainer.setContainerAtIndex(i, self.highlowcontainer.getContainerAtIndex(i).add(lowbits(x)))
	} else {
		newac := newArrayContainer()
		self.highlowcontainer.insertNewKeyValueAt(-i-1, hb, newac.add(lowbits(x)))
	}
}

func (self *RoaringBitmap) GetCardinality() int {
	size := 0
	for i := 0; i < self.highlowcontainer.size(); i++ {
		size += self.highlowcontainer.getContainerAtIndex(i).getCardinality()
	}
	return size
}

func (self *RoaringBitmap) And(x2 *RoaringBitmap) *RoaringBitmap {
	results := And(self, x2)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func (self *RoaringBitmap) Xor(x2 *RoaringBitmap) *RoaringBitmap {
	results := Xor(self, x2)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func (self *RoaringBitmap) Or(x2 *RoaringBitmap) *RoaringBitmap {
	results := Or(self, x2)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func (self *RoaringBitmap) AndNot(x2 *RoaringBitmap) *RoaringBitmap {
	results := AndNot(self, x2)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func Or(x1, x2 *RoaringBitmap) *RoaringBitmap {
	answer := NewRoaringBitmap()
	pos1 := 0
	pos2 := 0
	length1 := x1.highlowcontainer.size()
	length2 := x2.highlowcontainer.size()
main:
	for {
		if (pos1 < length1) && (pos2 < length2) {
			s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
			s2 := x2.highlowcontainer.getKeyAtIndex(pos2)

			for {
				if s1 < s2 {
					answer.highlowcontainer.appendCopy(x1.highlowcontainer, pos1)
					pos1++
					if pos1 == length1 {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
				} else if s1 > s2 {
					answer.highlowcontainer.appendCopy(x2.highlowcontainer, pos2)
					pos2++
					if pos2 == length2 {
						break main
					}
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				} else {

					answer.highlowcontainer.append(s1, x1.highlowcontainer.getContainerAtIndex(pos1).or(x2.highlowcontainer.getContainerAtIndex(pos2)))
					pos1++
					pos2++
					if (pos1 == length1) || (pos2 == length2) {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				}
			}
		} else {
			break
		}
	}
	if pos1 == length1 {
		answer.highlowcontainer.appendCopyMany(x2.highlowcontainer, pos2, length2)
	} else if pos2 == length2 {
		answer.highlowcontainer.appendCopyMany(x1.highlowcontainer, pos1, length1)
	}
	return answer
}

func And(x1, x2 *RoaringBitmap) *RoaringBitmap {
	answer := NewRoaringBitmap()
	pos1 := 0
	pos2 := 0
	length1 := x1.highlowcontainer.size()
	length2 := x2.highlowcontainer.size()
main:
	for {
		if pos1 < length1 && pos2 < length2 {
			s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
			s2 := x2.highlowcontainer.getKeyAtIndex(pos2)
			for {
				if s1 < s2 {
					pos1++
					if pos1 == length1 {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
				} else if s1 > s2 {
					pos2++
					if pos2 == length2 {
						break main
					}
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				} else {
					C := x1.highlowcontainer.getContainerAtIndex(pos1)
					C = C.and(x2.highlowcontainer.getContainerAtIndex(pos2))

					if C.getCardinality() > 0 {
						answer.highlowcontainer.append(s1, C)
					}
					pos1++
					pos2++
					if (pos1 == length1) || (pos2 == length2) {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)

				}
			}
		} else {
			break
		}
	}
	return answer
}
func Xor(x1, x2 *RoaringBitmap) *RoaringBitmap {
	answer := NewRoaringBitmap()
	pos1 := 0
	pos2 := 0
	length1 := x1.highlowcontainer.size()
	length2 := x2.highlowcontainer.size()

main:
	for {
		if (pos1 < length1) && (pos2 < length2) {
			s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
			s2 := x2.highlowcontainer.getKeyAtIndex(pos2)
			if s1 < s2 {
				answer.highlowcontainer.appendCopy(x1.highlowcontainer, pos1)

				pos1++
				if pos1 == length1 {
					break main
				}
				s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
			} else if s1 > s2 {
				answer.highlowcontainer.appendCopy(x2.highlowcontainer, pos2)
				pos2++
				if pos2 == length2 {
					break main
				}
				s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
			} else {
				c := x1.highlowcontainer.getContainerAtIndex(pos1).xor(x2.highlowcontainer.getContainerAtIndex(pos2))
				if c.getCardinality() > 0 {
					answer.highlowcontainer.append(s1, c)
				}
				pos1++
				pos2++
				if (pos1 == length1) || (pos2 == length2) {
					break main
				}
				s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
				s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
			}
		} else {
			break
		}
	}
	if pos1 == length1 {
		answer.highlowcontainer.appendCopyMany(x2.highlowcontainer, pos2, length2)
	} else if pos2 == length2 {
		answer.highlowcontainer.appendCopyMany(x1.highlowcontainer, pos1, length1)
	}
	return answer
}

func AndNot(x1, x2 *RoaringBitmap) *RoaringBitmap {
	answer := NewRoaringBitmap()
	pos1 := 0
	pos2 := 0
	length1 := x1.highlowcontainer.size()
	length2 := x2.highlowcontainer.size()

main:
	for {
		if pos1 < length1 && pos2 < length2 {
			s1 := x1.highlowcontainer.getKeyAtIndex(pos1)
			s2 := x2.highlowcontainer.getKeyAtIndex(pos2)
			for {
				if s1 < s2 {
					answer.highlowcontainer.appendCopy(x1.highlowcontainer, pos1)
					pos1++
					if pos1 == length1 {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
				} else if s1 > s2 {
					pos2++
					if pos2 == length2 {
						break main
					}
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				} else {
					C := x1.highlowcontainer.getContainerAtIndex(pos1)
					C.andNot(x2.highlowcontainer.getContainerAtIndex(pos2))
					if C.getCardinality() > 0 {
						answer.highlowcontainer.append(s1, C)
					}
					pos1++
					pos2++
					if (pos1 == length1) || (pos2 == length2) {
						break main
					}
					s1 = x1.highlowcontainer.getKeyAtIndex(pos1)
					s2 = x2.highlowcontainer.getKeyAtIndex(pos2)
				}
			}
		} else {
			break
		}
	}
	if pos2 == length2 {
		answer.highlowcontainer.appendCopyMany(x1.highlowcontainer, pos1, length1)
	}
	return answer
}

func BitmapOf(dat ...int) *RoaringBitmap {
	ans := NewRoaringBitmap()
	for _, i := range dat {
		ans.Add(i)
	}
	return ans
}

func (self *RoaringBitmap) Flip(rangeStart, rangeEnd int) *RoaringBitmap {
	results := Flip(self, rangeStart, rangeEnd)
	self.highlowcontainer = results.highlowcontainer
	return self
}

func Flip(bm *RoaringBitmap, rangeStart, rangeEnd int) *RoaringBitmap {
	if rangeStart >= rangeEnd {
		return bm.Clone()
	}

	answer := NewRoaringBitmap()
	hbStart := highbits(rangeStart)
	lbStart := lowbits(rangeStart)
	hbLast := highbits(rangeEnd - 1)
	lbLast := lowbits(rangeEnd - 1)

	// copy the containers before the active area
	answer.highlowcontainer.appendCopiesUntil(bm.highlowcontainer, hbStart)

	max := toIntUnsigned(maxLowBit())
	for hb := hbStart; hb <= hbLast; hb++ {
		containerStart := 0
		if hb == hbStart {
			containerStart = toIntUnsigned(lbStart)
		}
		containerLast := max
		if hb == hbLast {
			containerLast = toIntUnsigned(lbLast)
		}

		i := bm.highlowcontainer.getIndex(hb)
		j := answer.highlowcontainer.getIndex(hb)

		if i >= 0 {
			c := bm.highlowcontainer.getContainerAtIndex(i).not(containerStart, containerLast)
			if c.getCardinality() > 0 {
				answer.highlowcontainer.insertNewKeyValueAt(-j-1, hb, c)
			}

		} else { // *think* the range of ones must never be
			// empty.
			answer.highlowcontainer.insertNewKeyValueAt(-j-1, hb,
				rangeOfOnes(containerStart, containerLast))
		}
	}
	// copy the containers after the active area.
	answer.highlowcontainer.appendCopiesAfter(bm.highlowcontainer, hbLast)

	return answer
}

<|endoftext|>


<commit_before>package notetxt

import (
        "regexp"
        "strings"
        "os"
        "fmt"
        "bufio"
        "errors"
        "path"
        "path/filepath"
        "io/ioutil"
)
var title_clearer = regexp.MustCompile("[^a-zA-Z0-9\\s\\.\\-_]+")
var whitespace_clearer = regexp.MustCompile("\\s+")

func TitleToFilename (title string) string {

        // strip all non-conforming characters
        out := title_clearer.ReplaceAllString(title, "")

        // title shall be lowercase
        out = strings.ToLower(out)

        // every whitespace should become a space
        // if there are multiple whitespace insert only one space as a result
        out = whitespace_clearer.ReplaceAllString(out, " ")

        // every white space should become a dash (because they look nice)
        out = strings.Replace(out, " ", "-", -1)
        return out
}

var filename_regex = regexp.MustCompile("^[a-zA-Z0-9\\-\\.]+$")

func FilenameMatches(filename string) bool {
        return filename_regex.MatchString(filename)
}

type Note struct {
        Name string
        Filename string
        Tags []string
}

func readFilesInDir(dir string, subdir string) ([]string, []string) {
        var symlinks []string
        var files []string
        contents, _ := ioutil.ReadDir(dir + "/" + subdir)
        for _, f := range contents {
                if f.IsDir() {
                        t_files, t_syms := readFilesInDir(dir, subdir + "/" + f.Name())
                        files = append(files,t_files...)
                        symlinks = append(symlinks, t_syms...)
                } else {
                        if f.Mode() & os.ModeSymlink != 0 {
                                symlinks = append(symlinks, dir + subdir + "/" + f.Name())
                        } else {
                                files = append(files, dir + subdir + "/" + f.Name())
                        }
                }
        }
        return files, symlinks
}

func findTags(filename string, notedir string, symlinks []string) []string {
        var out []string

        plain_tag := strings.Replace(path.Dir(filename), notedir, "", 1)
        if len(plain_tag) != 0 {
                out = append(out, plain_tag)
        }

        for _, f := range symlinks {
                p, err := filepath.EvalSymlinks(f)
                if err != nil {
                        panic(err);
                }

                if p == filename {
                        out = append(out, strings.Replace(path.Dir(f), notedir, "", 1))
                }
        }

        return out
}

func ParseNote(notedir string, filename string, symlinks []string) (Note, error) {
        var note = Note{}
        note.Filename = filename

        f, err := os.Open(filename)
        if err != nil {
                return note, err
        }

        defer f.Close()
        reader := bufio.NewReaderSize(f, 4*1024)

        line, prefix, err := reader.ReadLine()
        if err != nil {
                return note, err
        }

        if prefix {
                return note, errors.New("Buffer reader too small for the name of the note.")
        }

        note.Name = string(line)

        note.Tags = findTags(filename, notedir, symlinks)

        return note, nil
}

func ParseDir(notedir string) ([]Note, error) {
        var notes []Note

        notedir, _ = filepath.Abs(notedir)
        files, symlinks := readFilesInDir(notedir, "")

        for _, f := range files {
                note, err := ParseNote(notedir, f, symlinks)
                if err != nil {
                        return nil, err
                }

                notes = append(notes, note)
        }

        return notes, nil
}

func CreateNote(title string, tag string, dir string) error {
        spacer := "\n" + strings.Repeat("=", len(title))
        text := title + spacer

        directory := fmt.Sprintf("%s/%s", dir, tag)
        os.MkdirAll(directory, 755)

        file := fmt.Sprintf("%s/%s.rst", directory, TitleToFilename(title))

        if _, err := os.Stat(file); err == nil {
                return errors.New("Notefile already exists. " +
                                "You can still edit it if you want.")
        }

        e := ioutil.WriteFile(file, []byte(text), 0644)
        if e != nil {
                return e
        }

        return nil
}
<commit_msg>update: Updated CreateNote function<commit_after>package notetxt

import (
        "regexp"
        "strings"
        "os"
        "fmt"
        "bufio"
        "errors"
        "path"
        "path/filepath"
        "io/ioutil"
)
var title_clearer = regexp.MustCompile("[^a-zA-Z0-9\\s\\.\\-_]+")
var whitespace_clearer = regexp.MustCompile("\\s+")

func TitleToFilename (title string) string {

        // strip all non-conforming characters
        out := title_clearer.ReplaceAllString(title, "")

        // title shall be lowercase
        out = strings.ToLower(out)

        // every whitespace should become a space
        // if there are multiple whitespace insert only one space as a result
        out = whitespace_clearer.ReplaceAllString(out, " ")

        // every white space should become a dash (because they look nice)
        out = strings.Replace(out, " ", "-", -1)
        return out
}

var filename_regex = regexp.MustCompile("^[a-zA-Z0-9\\-\\.]+$")

func FilenameMatches(filename string) bool {
        return filename_regex.MatchString(filename)
}

type Note struct {
        Name string
        Filename string
        Tags []string
}

func readFilesInDir(dir string, subdir string) ([]string, []string) {
        var symlinks []string
        var files []string
        contents, _ := ioutil.ReadDir(dir + "/" + subdir)
        for _, f := range contents {
                if f.IsDir() {
                        t_files, t_syms := readFilesInDir(dir, subdir + "/" + f.Name())
                        files = append(files,t_files...)
                        symlinks = append(symlinks, t_syms...)
                } else {
                        if f.Mode() & os.ModeSymlink != 0 {
                                symlinks = append(symlinks, dir + subdir + "/" + f.Name())
                        } else {
                                files = append(files, dir + subdir + "/" + f.Name())
                        }
                }
        }
        return files, symlinks
}

func findTags(filename string, notedir string, symlinks []string) []string {
        var out []string

        plain_tag := strings.Replace(path.Dir(filename), notedir, "", 1)
        if len(plain_tag) != 0 {
                out = append(out, plain_tag)
        }

        for _, f := range symlinks {
                p, err := filepath.EvalSymlinks(f)
                if err != nil {
                        panic(err);
                }

                if p == filename {
                        out = append(out, strings.Replace(path.Dir(f), notedir, "", 1))
                }
        }

        return out
}

func ParseNote(notedir string, filename string, symlinks []string) (Note, error) {
        var note = Note{}
        note.Filename = filename

        f, err := os.Open(filename)
        if err != nil {
                return note, err
        }

        defer f.Close()
        reader := bufio.NewReaderSize(f, 4*1024)

        line, prefix, err := reader.ReadLine()
        if err != nil {
                return note, err
        }

        if prefix {
                return note, errors.New("Buffer reader too small for the name of the note.")
        }

        note.Name = string(line)

        note.Tags = findTags(filename, notedir, symlinks)

        return note, nil
}

func ParseDir(notedir string) ([]Note, error) {
        var notes []Note

        notedir, _ = filepath.Abs(notedir)
        files, symlinks := readFilesInDir(notedir, "")

        for _, f := range files {
                note, err := ParseNote(notedir, f, symlinks)
                if err != nil {
                        return nil, err
                }

                notes = append(notes, note)
        }

        return notes, nil
}

func CreateNote(title string, tag string, dir string) (string, error) {
        spacer := "\n" + strings.Repeat("=", len(title))
        text := title + spacer

        directory := fmt.Sprintf("%s/%s", dir, tag)
        os.MkdirAll(directory, 755)

        file := fmt.Sprintf("%s/%s.rst", directory, TitleToFilename(title))

        if _, err := os.Stat(file); err == nil {
                return "", errors.New("Notefile already exists. " +
                                "You can still edit it if you want.")
        }

        e := ioutil.WriteFile(file, []byte(text), 0644)
        if e != nil {
                return "", e
        }

        return file, nil
}
<|endoftext|>


<commit_before>package rbac

import (
	"context"
	"strings"
	"time"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	kuser "k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/tools/cache"
	kauthenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
	kauthorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
	"k8s.io/kubernetes/pkg/apis/rbac"
	rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
	"k8s.io/kubernetes/pkg/apis/storage"
	"k8s.io/kubernetes/pkg/registry/rbac/validation"
	e2e "k8s.io/kubernetes/test/e2e/framework"

	"github.com/openshift/api/authorization"
	"github.com/openshift/api/build"
	"github.com/openshift/api/console"
	"github.com/openshift/api/image"
	"github.com/openshift/api/oauth"
	"github.com/openshift/api/project"
	"github.com/openshift/api/template"
	"github.com/openshift/api/user"

	exutil "github.com/openshift/origin/test/extended/util"
)

// copied from bootstrap policy
var read = []string{"get", "list", "watch"}

// copied from bootstrap policy
const (
	rbacGroup    = rbac.GroupName
	storageGroup = storage.GroupName
	kAuthzGroup  = kauthorizationapi.GroupName
	kAuthnGroup  = kauthenticationapi.GroupName

	authzGroup    = authorization.GroupName
	buildGroup    = build.GroupName
	imageGroup    = image.GroupName
	oauthGroup    = oauth.GroupName
	projectGroup  = project.GroupName
	templateGroup = template.GroupName
	userGroup     = user.GroupName
	consoleGroup  = console.GroupName

	legacyGroup         = ""
	legacyAuthzGroup    = ""
	legacyBuildGroup    = ""
	legacyImageGroup    = ""
	legacyProjectGroup  = ""
	legacyTemplateGroup = ""
	legacyUserGroup     = ""
	legacyOauthGroup    = ""

	// Provided as CRD via cluster-csi-snapshot-controller-operator
	snapshotGroup = "snapshot.storage.k8s.io"
)

// Do not change any of these lists without approval from the auth and master teams
// Most rules are copied from various cluster roles in bootstrap policy
var (
	allUnauthenticatedRules = []rbacv1.PolicyRule{
		rbacv1helpers.NewRule("get", "create").Groups(buildGroup, legacyBuildGroup).Resources("buildconfigs/webhooks").RuleOrDie(),

		rbacv1helpers.NewRule("impersonate").Groups(kAuthnGroup).Resources("userextras/scopes.authorization.openshift.io").RuleOrDie(),

		rbacv1helpers.NewRule("create").Groups(authzGroup, legacyAuthzGroup).Resources("selfsubjectrulesreviews").RuleOrDie(),

		rbacv1helpers.NewRule("create").Groups(kAuthzGroup).Resources("selfsubjectaccessreviews", "selfsubjectrulesreviews").RuleOrDie(),

		rbacv1helpers.NewRule("delete").Groups(oauthGroup, legacyOauthGroup).Resources("oauthaccesstokens", "oauthauthorizetokens").RuleOrDie(),

		// this is openshift specific
		rbacv1helpers.NewRule("get").URLs(
			"/version/openshift",
			"/.well-known",
			"/.well-known/*",
			"/.well-known/oauth-authorization-server",
		).RuleOrDie(),

		// TODO: remove with after 1.15 rebase
		rbacv1helpers.NewRule("get").URLs(
			"/readyz",
		).RuleOrDie(),

		// this is from upstream kube
		rbacv1helpers.NewRule("get").URLs(
			"/healthz", "/livez",
			"/version",
			"/version/",
		).RuleOrDie(),
	}

	allAuthenticatedRules = append(
		[]rbacv1.PolicyRule{
			rbacv1helpers.NewRule("create").Groups(buildGroup, legacyBuildGroup).Resources("builds/docker", "builds/optimizeddocker").RuleOrDie(),
			rbacv1helpers.NewRule("create").Groups(buildGroup, legacyBuildGroup).Resources("builds/jenkinspipeline").RuleOrDie(),
			rbacv1helpers.NewRule("create").Groups(buildGroup, legacyBuildGroup).Resources("builds/source").RuleOrDie(),

			rbacv1helpers.NewRule("get").Groups(userGroup, legacyUserGroup).Resources("users").Names("~").RuleOrDie(),
			rbacv1helpers.NewRule("list").Groups(projectGroup, legacyProjectGroup).Resources("projectrequests").RuleOrDie(),
			rbacv1helpers.NewRule("get", "list").Groups(authzGroup, legacyAuthzGroup).Resources("clusterroles").RuleOrDie(),
			rbacv1helpers.NewRule(read...).Groups(rbacGroup).Resources("clusterroles").RuleOrDie(),
			rbacv1helpers.NewRule("get", "list").Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
			rbacv1helpers.NewRule("get", "list", "watch").Groups(snapshotGroup).Resources("volumesnapshotclasses").RuleOrDie(),
			rbacv1helpers.NewRule("list", "watch").Groups(projectGroup, legacyProjectGroup).Resources("projects").RuleOrDie(),

			// These custom resources are used to extend console functionality
			// The console team is working on eliminating this exception in the near future
			rbacv1helpers.NewRule(read...).Groups(consoleGroup).Resources("consoleclidownloads", "consolelinks", "consoleexternalloglinks", "consolenotifications", "consoleyamlsamples").RuleOrDie(),

			// HelmChartRepository instances keep Helm chart repository configuration
			// By default users are able to browse charts from all configured repositories through console UI
			rbacv1helpers.NewRule("get", "list").Groups("helm.openshift.io").Resources("helmchartrepositories").RuleOrDie(),

			// TODO: remove when openshift-apiserver has removed these
			rbacv1helpers.NewRule("get").URLs(
				"/healthz/",
				"/oapi", "/oapi/*",
				"/osapi", "/osapi/",
				"/swaggerapi", "/swaggerapi/*", "/swagger.json", "/swagger-2.0.0.pb-v1",
				"/version/*",
				"/",
			).RuleOrDie(),

			// this is from upstream kube
			rbacv1helpers.NewRule("get").URLs(
				"/",
				"/openapi", "/openapi/*",
				"/api", "/api/*",
				"/apis", "/apis/*",
			).RuleOrDie(),
		},
		allUnauthenticatedRules...,
	)

	// group -> namespace -> rules
	groupNamespaceRules = map[string]map[string][]rbacv1.PolicyRule{
		kuser.AllAuthenticated: {
			"openshift": {
				rbacv1helpers.NewRule(read...).Groups(templateGroup, legacyTemplateGroup).Resources("templates").RuleOrDie(),
				rbacv1helpers.NewRule(read...).Groups(imageGroup, legacyImageGroup).Resources("imagestreams", "imagestreamtags", "imagestreamimages", "imagetags").RuleOrDie(),
				rbacv1helpers.NewRule("get").Groups(imageGroup, legacyImageGroup).Resources("imagestreams/layers").RuleOrDie(),
				rbacv1helpers.NewRule("get").Groups("").Resources("configmaps").RuleOrDie(),
			},
			"openshift-config-managed": {
				rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("configmaps").Names("console-public").RuleOrDie(),
			},
		},
		kuser.AllUnauthenticated:     {}, // no rules expect the cluster wide ones
		"system:authenticated:oauth": {}, // no rules expect the cluster wide ones
	}
)

var _ = g.Describe("[sig-auth][Feature:OpenShiftAuthorization] The default cluster RBAC policy", func() {
	defer g.GinkgoRecover()

	oc := exutil.NewCLI("default-rbac-policy")

	g.It("should have correct RBAC rules", func() {
		kubeInformers := informers.NewSharedInformerFactory(oc.AdminKubeClient(), 20*time.Minute)
		ruleResolver := exutil.NewRuleResolver(kubeInformers.Rbac().V1()) // signal what informers we want to use early

		stopCh := make(chan struct{})
		defer func() { close(stopCh) }()
		kubeInformers.Start(stopCh)

		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()

		if ok := cache.WaitForCacheSync(ctx.Done(),
			kubeInformers.Rbac().V1().ClusterRoles().Informer().HasSynced,
			kubeInformers.Rbac().V1().ClusterRoleBindings().Informer().HasSynced,
			kubeInformers.Rbac().V1().Roles().Informer().HasSynced,
			kubeInformers.Rbac().V1().RoleBindings().Informer().HasSynced,
		); !ok {
			exutil.FatalErr("failed to sync RBAC cache")
		}

		namespaces, err := oc.AdminKubeClient().CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
		if err != nil {
			exutil.FatalErr(err)
		}

		g.By("should only allow the system:authenticated group to access certain policy rules", func() {
			testAllGroupRules(ruleResolver, kuser.AllAuthenticated, allAuthenticatedRules, namespaces.Items)
		})

		g.By("should only allow the system:unauthenticated group to access certain policy rules", func() {
			testAllGroupRules(ruleResolver, kuser.AllUnauthenticated, allUnauthenticatedRules, namespaces.Items)
		})

		g.By("should only allow the system:authenticated:oauth group to access certain policy rules", func() {
			testAllGroupRules(ruleResolver, "system:authenticated:oauth", []rbacv1.PolicyRule{
				rbacv1helpers.NewRule("create").Groups(projectGroup, legacyProjectGroup).Resources("projectrequests").RuleOrDie(),
				rbacv1helpers.NewRule("get", "list", "watch", "delete").Groups(oauthGroup).Resources("useroauthaccesstokens").RuleOrDie(),
			}, namespaces.Items)
		})

	})
})

func testAllGroupRules(ruleResolver validation.AuthorizationRuleResolver, group string, expectedClusterRules []rbacv1.PolicyRule, namespaces []corev1.Namespace) {
	testGroupRules(ruleResolver, group, metav1.NamespaceNone, expectedClusterRules)

	for _, namespace := range namespaces {
		// merge the namespace scoped and cluster wide rules
		rules := append([]rbacv1.PolicyRule{}, groupNamespaceRules[group][namespace.Name]...)
		rules = append(rules, expectedClusterRules...)

		testGroupRules(ruleResolver, group, namespace.Name, rules)
	}
}

func testGroupRules(ruleResolver validation.AuthorizationRuleResolver, group, namespace string, expectedRules []rbacv1.PolicyRule) {
	actualRules, err := ruleResolver.RulesFor(&kuser.DefaultInfo{Groups: []string{group}}, namespace)
	o.Expect(err).NotTo(o.HaveOccurred()) // our default RBAC policy should never have rule resolution errors

	if cover, missing := validation.Covers(expectedRules, actualRules); !cover {
		e2e.Failf("%s has extra permissions in namespace %q:\n%s", group, namespace, rulesToString(missing))
	}

	// force test data to be cleaned up every so often but allow extra rules to not deadlock new changes
	if cover, missing := validation.Covers(actualRules, expectedRules); !cover {
		log := e2e.Logf
		if len(missing) > 15 {
			log = e2e.Failf
		}
		log("test data for %s has too many unnecessary permissions:\n%s", group, rulesToString(missing))
	}
}

func rulesToString(rules []rbacv1.PolicyRule) string {
	compactRules := rules
	if compact, err := validation.CompactRules(rules); err == nil {
		compactRules = compact
	}

	missingDescriptions := sets.NewString()
	for _, missing := range compactRules {
		missingDescriptions.Insert(rbacv1helpers.CompactString(missing))
	}

	return strings.Join(missingDescriptions.List(), "\n")
}
<commit_msg>Add consolequickstarts to list of console resource exceptions<commit_after>package rbac

import (
	"context"
	"strings"
	"time"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	kuser "k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/tools/cache"
	kauthenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
	kauthorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
	"k8s.io/kubernetes/pkg/apis/rbac"
	rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
	"k8s.io/kubernetes/pkg/apis/storage"
	"k8s.io/kubernetes/pkg/registry/rbac/validation"
	e2e "k8s.io/kubernetes/test/e2e/framework"

	"github.com/openshift/api/authorization"
	"github.com/openshift/api/build"
	"github.com/openshift/api/console"
	"github.com/openshift/api/image"
	"github.com/openshift/api/oauth"
	"github.com/openshift/api/project"
	"github.com/openshift/api/template"
	"github.com/openshift/api/user"

	exutil "github.com/openshift/origin/test/extended/util"
)

// copied from bootstrap policy
var read = []string{"get", "list", "watch"}

// copied from bootstrap policy
const (
	rbacGroup    = rbac.GroupName
	storageGroup = storage.GroupName
	kAuthzGroup  = kauthorizationapi.GroupName
	kAuthnGroup  = kauthenticationapi.GroupName

	authzGroup    = authorization.GroupName
	buildGroup    = build.GroupName
	imageGroup    = image.GroupName
	oauthGroup    = oauth.GroupName
	projectGroup  = project.GroupName
	templateGroup = template.GroupName
	userGroup     = user.GroupName
	consoleGroup  = console.GroupName

	legacyGroup         = ""
	legacyAuthzGroup    = ""
	legacyBuildGroup    = ""
	legacyImageGroup    = ""
	legacyProjectGroup  = ""
	legacyTemplateGroup = ""
	legacyUserGroup     = ""
	legacyOauthGroup    = ""

	// Provided as CRD via cluster-csi-snapshot-controller-operator
	snapshotGroup = "snapshot.storage.k8s.io"
)

// Do not change any of these lists without approval from the auth and master teams
// Most rules are copied from various cluster roles in bootstrap policy
var (
	allUnauthenticatedRules = []rbacv1.PolicyRule{
		rbacv1helpers.NewRule("get", "create").Groups(buildGroup, legacyBuildGroup).Resources("buildconfigs/webhooks").RuleOrDie(),

		rbacv1helpers.NewRule("impersonate").Groups(kAuthnGroup).Resources("userextras/scopes.authorization.openshift.io").RuleOrDie(),

		rbacv1helpers.NewRule("create").Groups(authzGroup, legacyAuthzGroup).Resources("selfsubjectrulesreviews").RuleOrDie(),

		rbacv1helpers.NewRule("create").Groups(kAuthzGroup).Resources("selfsubjectaccessreviews", "selfsubjectrulesreviews").RuleOrDie(),

		rbacv1helpers.NewRule("delete").Groups(oauthGroup, legacyOauthGroup).Resources("oauthaccesstokens", "oauthauthorizetokens").RuleOrDie(),

		// this is openshift specific
		rbacv1helpers.NewRule("get").URLs(
			"/version/openshift",
			"/.well-known",
			"/.well-known/*",
			"/.well-known/oauth-authorization-server",
		).RuleOrDie(),

		// TODO: remove with after 1.15 rebase
		rbacv1helpers.NewRule("get").URLs(
			"/readyz",
		).RuleOrDie(),

		// this is from upstream kube
		rbacv1helpers.NewRule("get").URLs(
			"/healthz", "/livez",
			"/version",
			"/version/",
		).RuleOrDie(),
	}

	allAuthenticatedRules = append(
		[]rbacv1.PolicyRule{
			rbacv1helpers.NewRule("create").Groups(buildGroup, legacyBuildGroup).Resources("builds/docker", "builds/optimizeddocker").RuleOrDie(),
			rbacv1helpers.NewRule("create").Groups(buildGroup, legacyBuildGroup).Resources("builds/jenkinspipeline").RuleOrDie(),
			rbacv1helpers.NewRule("create").Groups(buildGroup, legacyBuildGroup).Resources("builds/source").RuleOrDie(),

			rbacv1helpers.NewRule("get").Groups(userGroup, legacyUserGroup).Resources("users").Names("~").RuleOrDie(),
			rbacv1helpers.NewRule("list").Groups(projectGroup, legacyProjectGroup).Resources("projectrequests").RuleOrDie(),
			rbacv1helpers.NewRule("get", "list").Groups(authzGroup, legacyAuthzGroup).Resources("clusterroles").RuleOrDie(),
			rbacv1helpers.NewRule(read...).Groups(rbacGroup).Resources("clusterroles").RuleOrDie(),
			rbacv1helpers.NewRule("get", "list").Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
			rbacv1helpers.NewRule("get", "list", "watch").Groups(snapshotGroup).Resources("volumesnapshotclasses").RuleOrDie(),
			rbacv1helpers.NewRule("list", "watch").Groups(projectGroup, legacyProjectGroup).Resources("projects").RuleOrDie(),

			// These custom resources are used to extend console functionality
			// The console team is working on eliminating this exception in the near future
			rbacv1helpers.NewRule(read...).Groups(consoleGroup).Resources("consoleclidownloads", "consolelinks", "consoleexternalloglinks", "consolenotifications", "consoleyamlsamples", "consolequickstarts").RuleOrDie(),

			// HelmChartRepository instances keep Helm chart repository configuration
			// By default users are able to browse charts from all configured repositories through console UI
			rbacv1helpers.NewRule("get", "list").Groups("helm.openshift.io").Resources("helmchartrepositories").RuleOrDie(),

			// TODO: remove when openshift-apiserver has removed these
			rbacv1helpers.NewRule("get").URLs(
				"/healthz/",
				"/oapi", "/oapi/*",
				"/osapi", "/osapi/",
				"/swaggerapi", "/swaggerapi/*", "/swagger.json", "/swagger-2.0.0.pb-v1",
				"/version/*",
				"/",
			).RuleOrDie(),

			// this is from upstream kube
			rbacv1helpers.NewRule("get").URLs(
				"/",
				"/openapi", "/openapi/*",
				"/api", "/api/*",
				"/apis", "/apis/*",
			).RuleOrDie(),
		},
		allUnauthenticatedRules...,
	)

	// group -> namespace -> rules
	groupNamespaceRules = map[string]map[string][]rbacv1.PolicyRule{
		kuser.AllAuthenticated: {
			"openshift": {
				rbacv1helpers.NewRule(read...).Groups(templateGroup, legacyTemplateGroup).Resources("templates").RuleOrDie(),
				rbacv1helpers.NewRule(read...).Groups(imageGroup, legacyImageGroup).Resources("imagestreams", "imagestreamtags", "imagestreamimages", "imagetags").RuleOrDie(),
				rbacv1helpers.NewRule("get").Groups(imageGroup, legacyImageGroup).Resources("imagestreams/layers").RuleOrDie(),
				rbacv1helpers.NewRule("get").Groups("").Resources("configmaps").RuleOrDie(),
			},
			"openshift-config-managed": {
				rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("configmaps").Names("console-public").RuleOrDie(),
			},
		},
		kuser.AllUnauthenticated:     {}, // no rules expect the cluster wide ones
		"system:authenticated:oauth": {}, // no rules expect the cluster wide ones
	}
)

var _ = g.Describe("[sig-auth][Feature:OpenShiftAuthorization] The default cluster RBAC policy", func() {
	defer g.GinkgoRecover()

	oc := exutil.NewCLI("default-rbac-policy")

	g.It("should have correct RBAC rules", func() {
		kubeInformers := informers.NewSharedInformerFactory(oc.AdminKubeClient(), 20*time.Minute)
		ruleResolver := exutil.NewRuleResolver(kubeInformers.Rbac().V1()) // signal what informers we want to use early

		stopCh := make(chan struct{})
		defer func() { close(stopCh) }()
		kubeInformers.Start(stopCh)

		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()

		if ok := cache.WaitForCacheSync(ctx.Done(),
			kubeInformers.Rbac().V1().ClusterRoles().Informer().HasSynced,
			kubeInformers.Rbac().V1().ClusterRoleBindings().Informer().HasSynced,
			kubeInformers.Rbac().V1().Roles().Informer().HasSynced,
			kubeInformers.Rbac().V1().RoleBindings().Informer().HasSynced,
		); !ok {
			exutil.FatalErr("failed to sync RBAC cache")
		}

		namespaces, err := oc.AdminKubeClient().CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
		if err != nil {
			exutil.FatalErr(err)
		}

		g.By("should only allow the system:authenticated group to access certain policy rules", func() {
			testAllGroupRules(ruleResolver, kuser.AllAuthenticated, allAuthenticatedRules, namespaces.Items)
		})

		g.By("should only allow the system:unauthenticated group to access certain policy rules", func() {
			testAllGroupRules(ruleResolver, kuser.AllUnauthenticated, allUnauthenticatedRules, namespaces.Items)
		})

		g.By("should only allow the system:authenticated:oauth group to access certain policy rules", func() {
			testAllGroupRules(ruleResolver, "system:authenticated:oauth", []rbacv1.PolicyRule{
				rbacv1helpers.NewRule("create").Groups(projectGroup, legacyProjectGroup).Resources("projectrequests").RuleOrDie(),
				rbacv1helpers.NewRule("get", "list", "watch", "delete").Groups(oauthGroup).Resources("useroauthaccesstokens").RuleOrDie(),
			}, namespaces.Items)
		})

	})
})

func testAllGroupRules(ruleResolver validation.AuthorizationRuleResolver, group string, expectedClusterRules []rbacv1.PolicyRule, namespaces []corev1.Namespace) {
	testGroupRules(ruleResolver, group, metav1.NamespaceNone, expectedClusterRules)

	for _, namespace := range namespaces {
		// merge the namespace scoped and cluster wide rules
		rules := append([]rbacv1.PolicyRule{}, groupNamespaceRules[group][namespace.Name]...)
		rules = append(rules, expectedClusterRules...)

		testGroupRules(ruleResolver, group, namespace.Name, rules)
	}
}

func testGroupRules(ruleResolver validation.AuthorizationRuleResolver, group, namespace string, expectedRules []rbacv1.PolicyRule) {
	actualRules, err := ruleResolver.RulesFor(&kuser.DefaultInfo{Groups: []string{group}}, namespace)
	o.Expect(err).NotTo(o.HaveOccurred()) // our default RBAC policy should never have rule resolution errors

	if cover, missing := validation.Covers(expectedRules, actualRules); !cover {
		e2e.Failf("%s has extra permissions in namespace %q:\n%s", group, namespace, rulesToString(missing))
	}

	// force test data to be cleaned up every so often but allow extra rules to not deadlock new changes
	if cover, missing := validation.Covers(actualRules, expectedRules); !cover {
		log := e2e.Logf
		if len(missing) > 15 {
			log = e2e.Failf
		}
		log("test data for %s has too many unnecessary permissions:\n%s", group, rulesToString(missing))
	}
}

func rulesToString(rules []rbacv1.PolicyRule) string {
	compactRules := rules
	if compact, err := validation.CompactRules(rules); err == nil {
		compactRules = compact
	}

	missingDescriptions := sets.NewString()
	for _, missing := range compactRules {
		missingDescriptions.Insert(rbacv1helpers.CompactString(missing))
	}

	return strings.Join(missingDescriptions.List(), "\n")
}
<|endoftext|>