<commit_before>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"sync"
	"time"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/kbfs/kbfsblock"
	"github.com/pkg/errors"
	"golang.org/x/net/context"
)

const (
	// quotaServerTimeoutWhenCached defines how long we wait for the
	// quota usage to return from the server, when we've already read
	// it from the disk cache.
	quotaServerTimeoutWhenCached = 500 * time.Millisecond
)

// ECQUCtxTagKey is the type for unique ECQU background operation IDs.
type ECQUCtxTagKey struct{}

// ECQUID is used in EventuallyConsistentQuotaUsage for only background RPCs.
// More specifically, when we need to spawn a background goroutine for
// GetUserQuotaInfo, a new context with this tag is created and used. This is
// also used as a prefix for the logger module name in
// EventuallyConsistentQuotaUsage.
const ECQUID = "ECQU"

type cachedQuotaUsage struct {
	timestamp       time.Time
	usageBytes      int64
	archiveBytes    int64
	limitBytes      int64
	gitUsageBytes   int64
	gitArchiveBytes int64
	gitLimitBytes   int64
}

// EventuallyConsistentQuotaUsage keeps tracks of quota usage, in a way user of
// which can choose to accept stale data to reduce calls into block servers.
type EventuallyConsistentQuotaUsage struct {
	config  Config
	log     logger.Logger
	tid     keybase1.TeamID
	fetcher *fetchDecider

	mu     sync.RWMutex
	cached cachedQuotaUsage
}

// NewEventuallyConsistentQuotaUsage creates a new
// EventuallyConsistentQuotaUsage object.
func NewEventuallyConsistentQuotaUsage(
	config Config, loggerSuffix string) *EventuallyConsistentQuotaUsage {
	q := &EventuallyConsistentQuotaUsage{
		config: config,
		log:    config.MakeLogger(ECQUID + "-" + loggerSuffix),
	}
	q.fetcher = newFetchDecider(
		q.log, q.getAndCache, ECQUCtxTagKey{}, ECQUID, q.config)
	return q
}

// NewEventuallyConsistentTeamQuotaUsage creates a new
// EventuallyConsistentQuotaUsage object.
func NewEventuallyConsistentTeamQuotaUsage(
	config Config, tid keybase1.TeamID,
	loggerSuffix string) *EventuallyConsistentQuotaUsage {
	q := NewEventuallyConsistentQuotaUsage(config, loggerSuffix)
	q.tid = tid
	return q
}

func (q *EventuallyConsistentQuotaUsage) getCached() cachedQuotaUsage {
	q.mu.RLock()
	defer q.mu.RUnlock()
	return q.cached
}

func (q *EventuallyConsistentQuotaUsage) getID(
	ctx context.Context) (keybase1.UserOrTeamID, error) {
	if q.tid.IsNil() {
		session, err := q.config.KBPKI().GetCurrentSession(ctx)
		if err != nil {
			return keybase1.UserOrTeamID(""), err
		}
		return session.UID.AsUserOrTeam(), nil
	}
	return q.tid.AsUserOrTeam(), nil
}

func (q *EventuallyConsistentQuotaUsage) getAndCache(
	ctx context.Context) (err error) {
	defer func() {
		q.log.CDebugf(ctx, "getAndCache: error=%v", err)
	}()

	// Try pulling the quota from the disk cache.  If it exists, still
	// try the servers, but give it a short timeout.
	var quotaInfoFromCache *kbfsblock.QuotaInfo
	id, err := q.getID(ctx)
	if err != nil {
		return err
	}
	getCtx := ctx
	dqc := q.config.DiskQuotaCache()
	if dqc != nil {
		qi, err := dqc.Get(ctx, id)
		if err == nil {
			q.log.CDebugf(ctx, "Read quota for %s from disk cache", id)
			quotaInfoFromCache = &qi
			var cancel context.CancelFunc
			getCtx, cancel = context.WithTimeout(
				ctx, quotaServerTimeoutWhenCached)
			defer cancel()
		}
	}

	var quotaInfo *kbfsblock.QuotaInfo
	if q.tid.IsNil() {
		quotaInfo, err = q.config.BlockServer().GetUserQuotaInfo(getCtx)
	} else {
		quotaInfo, err = q.config.BlockServer().GetTeamQuotaInfo(getCtx, q.tid)
	}
	doCacheToDisk := dqc != nil
	switch err {
	case nil:
	case context.DeadlineExceeded:
		if quotaInfoFromCache != nil {
			q.log.CDebugf(ctx, "Can't contact server; using cached quota")
			quotaInfo = quotaInfoFromCache
			doCacheToDisk = false
		} else {
			return err
		}
	default:
		return err
	}

	q.mu.Lock()
	defer q.mu.Unlock()
	q.cached.limitBytes = quotaInfo.Limit
	q.cached.gitLimitBytes = quotaInfo.GitLimit
	if quotaInfo.Total != nil {
		q.cached.usageBytes = quotaInfo.Total.Bytes[kbfsblock.UsageWrite]
		q.cached.archiveBytes = quotaInfo.Total.Bytes[kbfsblock.UsageArchive]
		q.cached.gitUsageBytes = quotaInfo.Total.Bytes[kbfsblock.UsageGitWrite]
		q.cached.gitArchiveBytes =
			quotaInfo.Total.Bytes[kbfsblock.UsageGitArchive]
	} else {
		q.cached.usageBytes = 0
	}
	q.cached.timestamp = q.config.Clock().Now()

	if doCacheToDisk {
		err := dqc.Put(ctx, id, *quotaInfo)
		if err != nil {
			q.log.CDebugf(ctx, "Can't cache quota for %s: %+v", id, err)
		}
	}
	return nil
}

// Get returns KBFS bytes used and limit for user, for the current
// default block type. To help avoid having too frequent calls into
// bserver, caller can provide a positive tolerance, to accept stale
// LimitBytes and UsageBytes data. If tolerance is 0 or negative, this
// always makes a blocking RPC to bserver and return latest quota
// usage.
//
// 1) If the age of cached data is more than blockTolerance, a blocking RPC is
// issued and the function only returns after RPC finishes, with the newest
// data from RPC. The RPC causes cached data to be refreshed as well.
// 2) Otherwise, if the age of cached data is more than bgTolerance,
// a background RPC is spawned to refresh cached data, and the stale
// data is returned immediately.
// 3) Otherwise, the cached stale data is returned immediately.
func (q *EventuallyConsistentQuotaUsage) Get(
	ctx context.Context, bgTolerance, blockTolerance time.Duration) (
	timestamp time.Time, usageBytes, archiveBytes, limitBytes int64,
	err error) {
	c := q.getCached()
	err = q.fetcher.Do(ctx, bgTolerance, blockTolerance, c.timestamp)
	if err != nil {
		return time.Time{}, -1, -1, -1, err
	}

	c = q.getCached()
	switch q.config.DefaultBlockType() {
	case keybase1.BlockType_DATA:
		return c.timestamp, c.usageBytes, c.archiveBytes, c.limitBytes, nil
	case keybase1.BlockType_GIT:
		return c.timestamp, c.gitUsageBytes, c.gitArchiveBytes,
			c.gitLimitBytes, nil
	default:
		return time.Time{}, -1, -1, -1, errors.Errorf(
			"Unknown default block type: %d", q.config.DefaultBlockType())
	}
}

// GetAllTypes is the same as Get, except it returns usage and limits
// for all block types.
func (q *EventuallyConsistentQuotaUsage) GetAllTypes(
	ctx context.Context, bgTolerance, blockTolerance time.Duration) (
	timestamp time.Time,
	usageBytes, archiveBytes, limitBytes,
	gitUsageBytes, gitArchiveBytes, gitLimitBytes int64, err error) {
	c := q.getCached()
	err = q.fetcher.Do(ctx, bgTolerance, blockTolerance, c.timestamp)
	if err != nil {
		return time.Time{}, -1, -1, -1, -1, -1, -1, err
	}

	c = q.getCached()
	return c.timestamp,
		c.usageBytes, c.archiveBytes, c.limitBytes,
		c.gitUsageBytes, c.gitArchiveBytes, c.gitLimitBytes, nil
}
<commit_msg>quota_usage: launch background fetch if there's a timeout<commit_after>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"sync"
	"time"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/kbfs/kbfsblock"
	"github.com/pkg/errors"
	"golang.org/x/net/context"
)

const (
	// quotaServerTimeoutWhenCached defines how long we wait for the
	// quota usage to return from the server, when we've already read
	// it from the disk cache.
	quotaServerTimeoutWhenCached = 500 * time.Millisecond
)

// ECQUCtxTagKey is the type for unique ECQU background operation IDs.
type ECQUCtxTagKey struct{}

// ECQUID is used in EventuallyConsistentQuotaUsage for only background RPCs.
// More specifically, when we need to spawn a background goroutine for
// GetUserQuotaInfo, a new context with this tag is created and used. This is
// also used as a prefix for the logger module name in
// EventuallyConsistentQuotaUsage.
const ECQUID = "ECQU"

type cachedQuotaUsage struct {
	timestamp       time.Time
	usageBytes      int64
	archiveBytes    int64
	limitBytes      int64
	gitUsageBytes   int64
	gitArchiveBytes int64
	gitLimitBytes   int64
}

// EventuallyConsistentQuotaUsage keeps tracks of quota usage, in a way user of
// which can choose to accept stale data to reduce calls into block servers.
type EventuallyConsistentQuotaUsage struct {
	config  Config
	log     logger.Logger
	tid     keybase1.TeamID
	fetcher *fetchDecider

	mu      sync.RWMutex
	cached  cachedQuotaUsage
	bgFetch bool
}

// NewEventuallyConsistentQuotaUsage creates a new
// EventuallyConsistentQuotaUsage object.
func NewEventuallyConsistentQuotaUsage(
	config Config, loggerSuffix string) *EventuallyConsistentQuotaUsage {
	q := &EventuallyConsistentQuotaUsage{
		config: config,
		log:    config.MakeLogger(ECQUID + "-" + loggerSuffix),
	}
	q.fetcher = newFetchDecider(
		q.log, q.getAndCache, ECQUCtxTagKey{}, ECQUID, q.config)
	return q
}

// NewEventuallyConsistentTeamQuotaUsage creates a new
// EventuallyConsistentQuotaUsage object.
func NewEventuallyConsistentTeamQuotaUsage(
	config Config, tid keybase1.TeamID,
	loggerSuffix string) *EventuallyConsistentQuotaUsage {
	q := NewEventuallyConsistentQuotaUsage(config, loggerSuffix)
	q.tid = tid
	return q
}

func (q *EventuallyConsistentQuotaUsage) getCached() cachedQuotaUsage {
	q.mu.RLock()
	defer q.mu.RUnlock()
	return q.cached
}

func (q *EventuallyConsistentQuotaUsage) getID(
	ctx context.Context) (keybase1.UserOrTeamID, error) {
	if q.tid.IsNil() {
		session, err := q.config.KBPKI().GetCurrentSession(ctx)
		if err != nil {
			return keybase1.UserOrTeamID(""), err
		}
		return session.UID.AsUserOrTeam(), nil
	}
	return q.tid.AsUserOrTeam(), nil
}

func (q *EventuallyConsistentQuotaUsage) cache(
	ctx context.Context, quotaInfo *kbfsblock.QuotaInfo, doCacheToDisk bool) {
	q.mu.Lock()
	defer q.mu.Unlock()
	q.cached.limitBytes = quotaInfo.Limit
	q.cached.gitLimitBytes = quotaInfo.GitLimit
	if quotaInfo.Total != nil {
		q.cached.usageBytes = quotaInfo.Total.Bytes[kbfsblock.UsageWrite]
		q.cached.archiveBytes = quotaInfo.Total.Bytes[kbfsblock.UsageArchive]
		q.cached.gitUsageBytes = quotaInfo.Total.Bytes[kbfsblock.UsageGitWrite]
		q.cached.gitArchiveBytes =
			quotaInfo.Total.Bytes[kbfsblock.UsageGitArchive]
	} else {
		q.cached.usageBytes = 0
	}
	q.cached.timestamp = q.config.Clock().Now()

	dqc := q.config.DiskQuotaCache()
	if !doCacheToDisk || dqc == nil {
		return
	}

	id, err := q.getID(ctx)
	if err != nil {
		q.log.CDebugf(ctx, "Can't get ID: %+v", err)
		return
	}
	err = dqc.Put(ctx, id, *quotaInfo)
	if err != nil {
		q.log.CDebugf(ctx, "Can't cache quota for %s: %+v", id, err)
	}
}

func (q *EventuallyConsistentQuotaUsage) fetch(ctx context.Context) (
	quotaInfo *kbfsblock.QuotaInfo, err error) {
	if q.tid.IsNil() {
		return q.config.BlockServer().GetUserQuotaInfo(ctx)
	}
	return q.config.BlockServer().GetTeamQuotaInfo(ctx, q.tid)
}

func (q *EventuallyConsistentQuotaUsage) doBackgroundFetch() {
	doFetch := func() bool {
		q.mu.Lock()
		defer q.mu.Unlock()
		if q.bgFetch {
			return false
		}
		q.bgFetch = true
		return true
	}()
	if !doFetch {
		return
	}

	defer func() {
		q.mu.Lock()
		defer q.mu.Unlock()
		q.bgFetch = false
	}()

	ctx := CtxWithRandomIDReplayable(
		context.Background(), ECQUCtxTagKey{}, ECQUID, q.log)
	q.log.CDebugf(ctx, "Running background quota fetch, without a timeout")

	quotaInfo, err := q.fetch(ctx)
	if err != nil {
		q.log.CDebugf(ctx, "Unable to fetch quota in background: %+v", err)
		return
	}
	q.cache(ctx, quotaInfo, true)
}

func (q *EventuallyConsistentQuotaUsage) getAndCache(
	ctx context.Context) (err error) {
	defer func() {
		q.log.CDebugf(ctx, "getAndCache: error=%v", err)
	}()

	// Try pulling the quota from the disk cache.  If it exists, still
	// try the servers, but give it a short timeout.
	var quotaInfoFromCache *kbfsblock.QuotaInfo
	id, err := q.getID(ctx)
	if err != nil {
		return err
	}
	getCtx := ctx
	dqc := q.config.DiskQuotaCache()
	if dqc != nil {
		qi, err := dqc.Get(ctx, id)
		if err == nil {
			q.log.CDebugf(ctx, "Read quota for %s from disk cache", id)
			quotaInfoFromCache = &qi
			var cancel context.CancelFunc
			getCtx, cancel = context.WithTimeout(
				ctx, quotaServerTimeoutWhenCached)
			defer cancel()
		}
	}

	quotaInfo, err := q.fetch(getCtx)
	doCacheToDisk := dqc != nil
	switch err {
	case nil:
	case context.DeadlineExceeded:
		go q.doBackgroundFetch()
		if quotaInfoFromCache != nil {
			q.log.CDebugf(ctx, "Can't contact server; using cached quota")
			quotaInfo = quotaInfoFromCache
			doCacheToDisk = false
		} else {
			return err
		}
	default:
		return err
	}

	q.cache(ctx, quotaInfo, doCacheToDisk)
	return nil
}

// Get returns KBFS bytes used and limit for user, for the current
// default block type. To help avoid having too frequent calls into
// bserver, caller can provide a positive tolerance, to accept stale
// LimitBytes and UsageBytes data. If tolerance is 0 or negative, this
// always makes a blocking RPC to bserver and return latest quota
// usage.
//
// 1) If the age of cached data is more than blockTolerance, a blocking RPC is
// issued and the function only returns after RPC finishes, with the newest
// data from RPC. The RPC causes cached data to be refreshed as well.
// 2) Otherwise, if the age of cached data is more than bgTolerance,
// a background RPC is spawned to refresh cached data, and the stale
// data is returned immediately.
// 3) Otherwise, the cached stale data is returned immediately.
func (q *EventuallyConsistentQuotaUsage) Get(
	ctx context.Context, bgTolerance, blockTolerance time.Duration) (
	timestamp time.Time, usageBytes, archiveBytes, limitBytes int64,
	err error) {
	c := q.getCached()
	err = q.fetcher.Do(ctx, bgTolerance, blockTolerance, c.timestamp)
	if err != nil {
		return time.Time{}, -1, -1, -1, err
	}

	c = q.getCached()
	switch q.config.DefaultBlockType() {
	case keybase1.BlockType_DATA:
		return c.timestamp, c.usageBytes, c.archiveBytes, c.limitBytes, nil
	case keybase1.BlockType_GIT:
		return c.timestamp, c.gitUsageBytes, c.gitArchiveBytes,
			c.gitLimitBytes, nil
	default:
		return time.Time{}, -1, -1, -1, errors.Errorf(
			"Unknown default block type: %d", q.config.DefaultBlockType())
	}
}

// GetAllTypes is the same as Get, except it returns usage and limits
// for all block types.
func (q *EventuallyConsistentQuotaUsage) GetAllTypes(
	ctx context.Context, bgTolerance, blockTolerance time.Duration) (
	timestamp time.Time,
	usageBytes, archiveBytes, limitBytes,
	gitUsageBytes, gitArchiveBytes, gitLimitBytes int64, err error) {
	c := q.getCached()
	err = q.fetcher.Do(ctx, bgTolerance, blockTolerance, c.timestamp)
	if err != nil {
		return time.Time{}, -1, -1, -1, -1, -1, -1, err
	}

	c = q.getCached()
	return c.timestamp,
		c.usageBytes, c.archiveBytes, c.limitBytes,
		c.gitUsageBytes, c.gitArchiveBytes, c.gitLimitBytes, nil
}
<|endoftext|>


<commit_before>package move

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/instance/lifecycle"
	"github.com/cozy/cozy-stack/model/job"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/jsonapi"
)

// ImportOptions contains the options for launching the import worker.
type ImportOptions struct {
	SettingsURL string `json:"url,omitempty"`
	ManifestURL string `json:"manifest_url,omitempty"`
}

// CheckImport returns an error if an exports cannot be found at the given URL,
// or if the instance has not enough disk space to import the files.
func CheckImport(inst *instance.Instance, settingsURL string) error {
	manifestURL, err := transformSettingsURLToManifestURL(settingsURL)
	if err != nil {
		inst.Logger().WithField("nspace", "move").
			Debugf("Invalid settings URL %s: %s", settingsURL, err)
		return ErrExportNotFound
	}
	manifest, err := fetchManifest(manifestURL)
	if err != nil {
		inst.Logger().WithField("nspace", "move").
			Warnf("Cannot fetch manifest: %s", err)
		return ErrExportNotFound
	}
	if inst.BytesDiskQuota > 0 && manifest.TotalSize > inst.BytesDiskQuota {
		return ErrNotEnoughSpace
	}
	return nil
}

// ScheduleImport blocks the instance and adds a job to import the data from
// the given URL.
func ScheduleImport(inst *instance.Instance, options ImportOptions) error {
	manifestURL, err := transformSettingsURLToManifestURL(options.SettingsURL)
	if err != nil {
		return ErrExportNotFound
	}
	options.ManifestURL = manifestURL
	options.SettingsURL = ""
	msg, err := job.NewMessage(options)
	if err != nil {
		return err
	}
	_, err = job.System().PushJob(inst, &job.JobRequest{
		WorkerType: "import",
		Message:    msg,
	})
	if err != nil {
		return err
	}

	settings, err := inst.SettingsDocument()
	if err != nil {
		return nil
	}
	settings.M["importing"] = true
	_ = couchdb.UpdateDoc(inst, settings)
	return nil
}

func transformSettingsURLToManifestURL(settingsURL string) (string, error) {
	u, err := url.Parse(strings.TrimSpace(settingsURL))
	if err != nil {
		return "", err
	}
	if strings.HasPrefix(u.Host, consts.SettingsSlug+".") {
		// Nested subdomains
		u.Host = strings.TrimPrefix(u.Host, consts.SettingsSlug+".")
	} else {
		// Flat subdomains
		parts := strings.Split(u.Host, ".")
		parts[0] = strings.TrimSuffix(u.Host, "-"+consts.SettingsSlug)
		u.Host = strings.Join(parts, ".")
	}
	if !strings.HasPrefix(u.Fragment, "/exports/") {
		return "", fmt.Errorf("Fragment is not in the expected format")
	}
	mac := strings.TrimPrefix(u.Fragment, "/exports/")
	u.Fragment = ""
	u.Path = "/move/exports/" + mac
	u.RawPath = ""
	u.RawQuery = ""
	return u.String(), nil
}

func fetchManifest(manifestURL string) (*ExportDoc, error) {
	res, err := http.Get(manifestURL)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		return nil, ErrExportNotFound
	}
	doc := &ExportDoc{}
	if _, err = jsonapi.Bind(res.Body, doc); err != nil {
		return nil, err
	}
	if doc.State != ExportStateDone {
		return nil, ErrExportNotFound
	}
	return doc, nil
}

// Import downloads the documents and files from an export and add them to the
// local instance. It returns the list of slugs for apps/konnectors that have
// not been installed.
func Import(inst *instance.Instance, options ImportOptions) ([]string, error) {
	defer func() {
		settings, err := inst.SettingsDocument()
		if err == nil {
			delete(settings.M, "importing")
			_ = couchdb.UpdateDoc(inst, settings)
		}
	}()

	doc, err := fetchManifest(options.ManifestURL)
	if err != nil {
		return nil, err
	}

	if err = lifecycle.Reset(inst); err != nil {
		return nil, err
	}

	im := &importer{
		inst:    inst,
		fs:      inst.VFS(),
		options: options,
		doc:     doc,
	}
	if err = im.importPart(""); err != nil {
		return nil, err
	}
	for _, cursor := range doc.PartsCursors {
		if err = im.importPart(cursor); err != nil {
			return nil, err
		}
	}

	return im.appsNotInstalled, nil
}

// ImportIsFinished returns true unless an import is running
func ImportIsFinished(inst *instance.Instance) bool {
	settings, err := inst.SettingsDocument()
	if err != nil {
		return false
	}
	importing, _ := settings.M["importing"].(bool)
	return !importing
}
<commit_msg>Fix import for flat subdomains URL<commit_after>package move

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/instance/lifecycle"
	"github.com/cozy/cozy-stack/model/job"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/jsonapi"
)

// ImportOptions contains the options for launching the import worker.
type ImportOptions struct {
	SettingsURL string `json:"url,omitempty"`
	ManifestURL string `json:"manifest_url,omitempty"`
}

// CheckImport returns an error if an exports cannot be found at the given URL,
// or if the instance has not enough disk space to import the files.
func CheckImport(inst *instance.Instance, settingsURL string) error {
	manifestURL, err := transformSettingsURLToManifestURL(settingsURL)
	if err != nil {
		inst.Logger().WithField("nspace", "move").
			Debugf("Invalid settings URL %s: %s", settingsURL, err)
		return ErrExportNotFound
	}
	manifest, err := fetchManifest(manifestURL)
	if err != nil {
		inst.Logger().WithField("nspace", "move").
			Warnf("Cannot fetch manifest: %s", err)
		return ErrExportNotFound
	}
	if inst.BytesDiskQuota > 0 && manifest.TotalSize > inst.BytesDiskQuota {
		return ErrNotEnoughSpace
	}
	return nil
}

// ScheduleImport blocks the instance and adds a job to import the data from
// the given URL.
func ScheduleImport(inst *instance.Instance, options ImportOptions) error {
	manifestURL, err := transformSettingsURLToManifestURL(options.SettingsURL)
	if err != nil {
		return ErrExportNotFound
	}
	options.ManifestURL = manifestURL
	options.SettingsURL = ""
	msg, err := job.NewMessage(options)
	if err != nil {
		return err
	}
	_, err = job.System().PushJob(inst, &job.JobRequest{
		WorkerType: "import",
		Message:    msg,
	})
	if err != nil {
		return err
	}

	settings, err := inst.SettingsDocument()
	if err != nil {
		return nil
	}
	settings.M["importing"] = true
	_ = couchdb.UpdateDoc(inst, settings)
	return nil
}

func transformSettingsURLToManifestURL(settingsURL string) (string, error) {
	u, err := url.Parse(strings.TrimSpace(settingsURL))
	if err != nil {
		return "", err
	}
	if strings.HasPrefix(u.Host, consts.SettingsSlug+".") {
		// Nested subdomains
		u.Host = strings.TrimPrefix(u.Host, consts.SettingsSlug+".")
	} else {
		// Flat subdomains
		parts := strings.Split(u.Host, ".")
		parts[0] = strings.TrimSuffix(parts[0], "-"+consts.SettingsSlug)
		u.Host = strings.Join(parts, ".")
	}
	if !strings.HasPrefix(u.Fragment, "/exports/") {
		return "", fmt.Errorf("Fragment is not in the expected format")
	}
	mac := strings.TrimPrefix(u.Fragment, "/exports/")
	u.Fragment = ""
	u.Path = "/move/exports/" + mac
	u.RawPath = ""
	u.RawQuery = ""
	return u.String(), nil
}

func fetchManifest(manifestURL string) (*ExportDoc, error) {
	res, err := http.Get(manifestURL)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		return nil, ErrExportNotFound
	}
	doc := &ExportDoc{}
	if _, err = jsonapi.Bind(res.Body, doc); err != nil {
		return nil, err
	}
	if doc.State != ExportStateDone {
		return nil, ErrExportNotFound
	}
	return doc, nil
}

// Import downloads the documents and files from an export and add them to the
// local instance. It returns the list of slugs for apps/konnectors that have
// not been installed.
func Import(inst *instance.Instance, options ImportOptions) ([]string, error) {
	defer func() {
		settings, err := inst.SettingsDocument()
		if err == nil {
			delete(settings.M, "importing")
			_ = couchdb.UpdateDoc(inst, settings)
		}
	}()

	doc, err := fetchManifest(options.ManifestURL)
	if err != nil {
		return nil, err
	}

	if err = lifecycle.Reset(inst); err != nil {
		return nil, err
	}

	im := &importer{
		inst:    inst,
		fs:      inst.VFS(),
		options: options,
		doc:     doc,
	}
	if err = im.importPart(""); err != nil {
		return nil, err
	}
	for _, cursor := range doc.PartsCursors {
		if err = im.importPart(cursor); err != nil {
			return nil, err
		}
	}

	return im.appsNotInstalled, nil
}

// ImportIsFinished returns true unless an import is running
func ImportIsFinished(inst *instance.Instance) bool {
	settings, err := inst.SettingsDocument()
	if err != nil {
		return false
	}
	importing, _ := settings.M["importing"].(bool)
	return !importing
}
<|endoftext|>


<commit_before>package testdata

import (
	"os"
	"testing"
	"time"

	"github.com/go-test/deep"
	"github.com/google/mtail/metrics"
	"github.com/google/mtail/metrics/datum"
)

var expectedMetrics = []*metrics.Metric{
	&metrics.Metric{
		Name:    "bytes_total",
		Program: "reader_test",
		Kind:    metrics.Counter,
		Keys:    []string{"operation"},
		LabelValues: []*metrics.LabelValue{
			&metrics.LabelValue{
				Labels: []string{"sent"},
				Value:  datum.MakeInt(62793673, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC))},
			&metrics.LabelValue{
				Labels: []string{"received"},
				Value:  datum.MakeInt(975017, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC))}}},
	&metrics.Metric{
		Name:    "connections_total",
		Program: "reader_test",
		Kind:    metrics.Counter,
		Keys:    []string{},
		LabelValues: []*metrics.LabelValue{
			&metrics.LabelValue{
				Value: datum.MakeInt(52, time.Date(2011, 2, 22, 21, 54, 13, 0, time.UTC))}}},
	&metrics.Metric{
		Name:    "connection-time_total",
		Program: "reader_test",
		Kind:    metrics.Counter,
		Keys:    []string{},
		LabelValues: []*metrics.LabelValue{
			&metrics.LabelValue{
				Value: datum.MakeInt(1181011, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC))}}},
	&metrics.Metric{
		Name:    "transfers_total",
		Program: "reader_test",
		Kind:    metrics.Counter,
		Keys:    []string{"operation", "module"},
		LabelValues: []*metrics.LabelValue{
			&metrics.LabelValue{
				Labels: []string{"send", "module"},
				Value:  datum.MakeInt(2, time.Date(2011, 2, 23, 5, 50, 32, 0, time.UTC))},
			&metrics.LabelValue{
				Labels: []string{"send", "repo"},
				Value:  datum.MakeInt(25, time.Date(2011, 2, 23, 5, 51, 14, 0, time.UTC))}}},
	&metrics.Metric{
		Name:        "foo",
		Program:     "reader_test",
		Kind:        metrics.Gauge,
		Keys:        []string{"label"},
		LabelValues: []*metrics.LabelValue{},
	},
	&metrics.Metric{
		Name:    "bar",
		Program: "reader_test",
		Kind:    metrics.Counter,
		Keys:    []string{},
		LabelValues: []*metrics.LabelValue{
			&metrics.LabelValue{
				Value: datum.MakeInt(0, time.Unix(0, 0)),
			},
		},
	},
	&metrics.Metric{
		Name:    "floaty",
		Program: "reader_test",
		Kind:    metrics.Gauge,
		Type:    datum.Float,
		Keys:    []string{},
		LabelValues: []*metrics.LabelValue{
			&metrics.LabelValue{
				Value: datum.MakeFloat(37.0, time.Date(2017, 6, 15, 18, 9, 37, 0, time.UTC)),
			},
		},
	},
}

func TestReadTestData(t *testing.T) {
	f, err := os.Open("reader_test.golden")
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()
	store := metrics.NewStore()
	ReadTestData(f, "reader_test", store)
	diff := deep.Equal(expectedMetrics, store.Metrics)
	if diff != nil {
		t.Errorf("metrics don't match: %s\n", diff)
		t.Logf("store contains %s", store.Metrics)
	}
}
<commit_msg>Fixes the reader test to suite the format of a metric store.<commit_after>package testdata

import (
	"os"
	"testing"
	"time"

	"github.com/go-test/deep"
	"github.com/google/mtail/metrics"
	"github.com/google/mtail/metrics/datum"
)

var expectedMetrics = map[string][]*metrics.Metric{
	"bytes_total": {
		&metrics.Metric{
			Name:    "bytes_total",
			Program: "reader_test",
			Kind:    metrics.Counter,
			Keys:    []string{"operation"},
			LabelValues: []*metrics.LabelValue{
				&metrics.LabelValue{
					Labels: []string{"sent"},
					Value:  datum.MakeInt(62793673, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC))},
				&metrics.LabelValue{
					Labels: []string{"received"},
					Value:  datum.MakeInt(975017, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC))}}},
	},
	"connections_total": {
		&metrics.Metric{
			Name:    "connections_total",
			Program: "reader_test",
			Kind:    metrics.Counter,
			Keys:    []string{},
			LabelValues: []*metrics.LabelValue{
				&metrics.LabelValue{
					Value: datum.MakeInt(52, time.Date(2011, 2, 22, 21, 54, 13, 0, time.UTC))}}},
	},
	"connection-time_total": {
		&metrics.Metric{
			Name:    "connection-time_total",
			Program: "reader_test",
			Kind:    metrics.Counter,
			Keys:    []string{},
			LabelValues: []*metrics.LabelValue{
				&metrics.LabelValue{
					Value: datum.MakeInt(1181011, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC))}}},
	},
	"transfers_total": {
		&metrics.Metric{
			Name:    "transfers_total",
			Program: "reader_test",
			Kind:    metrics.Counter,
			Keys:    []string{"operation", "module"},
			LabelValues: []*metrics.LabelValue{
				&metrics.LabelValue{
					Labels: []string{"send", "module"},
					Value:  datum.MakeInt(2, time.Date(2011, 2, 23, 5, 50, 32, 0, time.UTC))},
				&metrics.LabelValue{
					Labels: []string{"send", "repo"},
					Value:  datum.MakeInt(25, time.Date(2011, 2, 23, 5, 51, 14, 0, time.UTC))}}},
	},
	"foo": {
		&metrics.Metric{
			Name:        "foo",
			Program:     "reader_test",
			Kind:        metrics.Gauge,
			Keys:        []string{"label"},
			LabelValues: []*metrics.LabelValue{},
		},
	},
	"bar": {
		&metrics.Metric{
			Name:    "bar",
			Program: "reader_test",
			Kind:    metrics.Counter,
			Keys:    []string{},
			LabelValues: []*metrics.LabelValue{
				&metrics.LabelValue{
					Value: datum.MakeInt(0, time.Unix(0, 0)),
				},
			},
		},
	},
	"floaty": {
		&metrics.Metric{
			Name:    "floaty",
			Program: "reader_test",
			Kind:    metrics.Gauge,
			Type:    datum.Float,
			Keys:    []string{},
			LabelValues: []*metrics.LabelValue{
				&metrics.LabelValue{
					Labels: []string{},
					Value:  datum.MakeFloat(37.0, time.Date(2017, 6, 15, 18, 9, 37, 0, time.UTC)),
				},
			},
		},
	},
}

func TestReadTestData(t *testing.T) {
	f, err := os.Open("reader_test.golden")
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()
	store := metrics.NewStore()
	ReadTestData(f, "reader_test", store)
	diff := deep.Equal(expectedMetrics, store.Metrics)
	if diff != nil {
		t.Error(diff)
		t.Logf("store contains %s", store.Metrics)
	}
}
<|endoftext|>


<commit_before>package httpauth

import (
    "bytes"
    "fmt"
    "os"
    "testing"
    "gopkg.in/mgo.v2"
    //"gopkg.in/mgo.v2/bson"
)

var (
    backend         MongodbAuthBackend
    url = "mongodb://localhost/"
    db = "test"
)

func TestMongodbInit(t *testing.T) {
    con, err := mgo.Dial(url)
    if err != nil {
        t.Errorf("Couldn't set up test mongodb session: %v", err)
        fmt.Printf("Couldn't set up test mongodb session: %v\n", err)
        os.Exit(1)
    }
    err = con.Ping()
    if err != nil {
        t.Errorf("Couldn't ping test mongodb database: %v", err)
        fmt.Printf("Couldn't ping test mongodb database: %v\n", err)
        // t.Errorf("Couldn't ping test database: %v\n", err)
        os.Exit(1)
    }
    db := con.DB(db)
    err = db.DropDatabase()
    if err != nil {
        t.Errorf("Couldn't drop test mongodb database: %v", err)
        fmt.Printf("Couldn't drop test mongodb database: %v\n", err)
        // t.Errorf("Couldn't ping test database: %v\n", err)
        os.Exit(1)
    }
}

func TestNewMongodbAuthBackend(t *testing.T) {
    backend, err := NewMongodbBackend(url, db)
    if err != nil {
        t.Fatalf("NewMongodbBackend error: %v", err)
    }
    if backend.mongoUrl != url {
        t.Fatal("Url name.")
    }
    if backend.database != db {
        t.Fatal("DB not saved.")
    }
}

func TestSaveUser_mongodb(t *testing.T) {
    user2 := UserData{"username2", "email2", []byte("passwordhash2"), "role2"}
    if err := backend.SaveUser(user2); err != nil {
        t.Fatalf("SaveUser mongodb error: %v", err)
    }

    user := UserData{"username", "email", []byte("passwordhash"), "role"}
    if err := backend.SaveUser(user); err != nil {
        t.Fatalf("SaveUser mongodb error: %v", err)
    }
}

func TestNewMongodbAuthBackend_existing(t *testing.T) {
    b2, err := NewMongodbBackend(driverName, driverInfo)
    if err != nil {
        t.Fatalf("NewMongodbBackend (existing) error: %v", err)
    }

    user, ok := b2.User("username")
    if !ok {
        t.Fatal("Secondary backend failed")
    }
    if user.Username != "username" {
        t.Fatal("Username not correct.")
    }
    if user.Email != "email" {
        t.Fatal("User email not correct.")
    }
    if !bytes.Equal(user.Hash, []byte("passwordhash")) {
        t.Fatal("User password not correct.")
    }
}

func TestUser_existing_mongodb(t *testing.T) {
    if user, ok := backend.User("username"); ok {
        if user.Username != "username" {
            t.Fatal("Username not correct.")
        }
        if user.Email != "email" {
            t.Fatal("User email not correct.")
        }
        if !bytes.Equal(user.Hash, []byte("passwordhash")) {
            t.Fatal("User password not correct.")
        }
    } else {
        t.Fatal("User not found")
    }
    if user, ok := backend.User("username2"); ok {
        if user.Username != "username2" {
            t.Fatal("Username not correct.")
        }
        if user.Email != "email2" {
            t.Fatal("User email not correct.")
        }
        if !bytes.Equal(user.Hash, []byte("passwordhash2")) {
            t.Fatal("User password not correct.")
        }
    } else {
        t.Fatal("User not found")
    }
}

func TestUser_notexisting_mongodb(t *testing.T) {
    if _, ok := backend.User("notexist"); ok {
        t.Fatal("Not existing user found.")
    }
}

func TestUsers_mongodb(t *testing.T) {
    var (
        u1 UserData
        u2 UserData
    )
    users := backend.Users()
    if len(users) != 2 {
        t.Fatal("Wrong amount of users found.")
    }
    if users[0].Username == "username" {
        u1 = users[0]
        u2 = users[1]
    } else if users[1].Username == "username" {
        u1 = users[1]
        u2 = users[0]
    } else {
        t.Fatal("One of the users not found.")
    }

    if u1.Username != "username" {
        t.Fatal("Username not correct.")
    }
    if u1.Email != "email" {
        t.Fatal("User email not correct.")
    }
    if !bytes.Equal(u1.Hash, []byte("passwordhash")) {
        t.Fatal("User password not correct.")
    }
    if u2.Username != "username2" {
        t.Fatal("Username not correct.")
    }
    if u2.Email != "email2" {
        t.Fatal("User email not correct.")
    }
    if !bytes.Equal(u2.Hash, []byte("passwordhash2")) {
        t.Fatal("User password not correct.")
    }
}

func TestUpdateUser_mongodb(t *testing.T) {
    user2 := UserData{"username", "newemail", []byte("newpassword"), "newrole"}
    if err := backend.SaveUser(user2); err != nil {
        t.Fatalf("SaveUser mongodb error: %v", err)
    }
    u2, ok := backend.User("username")
    if !ok {
        t.Fatal("Updated user not found")
    }
    if u2.Username != "username" {
        t.Fatal("Username not correct.")
    }
    if u2.Email != "newemail" {
        t.Fatal("User email not correct.")
    }
    if u2.Role != "newrole" {
        t.Fatalf("User role not correct: found %v, expected %v", u2.Role, "newrole");
    }
    if !bytes.Equal(u2.Hash, []byte("newpassword")) {
        t.Fatal("User password not correct.")
    }
}

func TestMongodbDeleteUser_mongodb(t *testing.T) {
    if err := backend.DeleteUser("username"); err != nil {
        t.Fatalf("DeleteUser error: %v", err)
    }
    if err := backend.DeleteUser("username"); err != nil {
        t.Fatalf("DeleteUser error: %v", err)
    }

    if err := backend.DeleteUser("username2"); err != nil {
        t.Fatalf("DeleteUser error: %v", err)
    }
}

<commit_msg>added travis yml<commit_after>package httpauth

import (
    "bytes"
    "fmt"
    "os"
    "testing"
    "gopkg.in/mgo.v2"
    //"gopkg.in/mgo.v2/bson"
)

var (
    backend         MongodbAuthBackend
    url = "mongodb://localhost/"
    db = "test"
)

func TestMongodbInit(t *testing.T) {
    con, err := mgo.Dial(url)
    if err != nil {
        t.Errorf("Couldn't set up test mongodb session: %v\nHave you started the mongo db?\n```\n$ mongod --dbpath mongodbtest/\n```", err)
        fmt.Printf("Couldn't set up test mongodb session: %v\nHave you started the mongo db?\n```\n$ mongod --dbpath mongodbtest/\n```\n", err)
        os.Exit(1)
    }
    err = con.Ping()
    if err != nil {
        t.Errorf("Couldn't ping test mongodb database: %v", err)
        fmt.Printf("Couldn't ping test mongodb database: %v\n", err)
        // t.Errorf("Couldn't ping test database: %v\n", err)
        os.Exit(1)
    }
    db := con.DB(db)
    err = db.DropDatabase()
    if err != nil {
        t.Errorf("Couldn't drop test mongodb database: %v", err)
        fmt.Printf("Couldn't drop test mongodb database: %v\n", err)
        // t.Errorf("Couldn't ping test database: %v\n", err)
        os.Exit(1)
    }
}

func TestNewMongodbAuthBackend(t *testing.T) {
    backend, err := NewMongodbBackend(url, db)
    if err != nil {
        t.Fatalf("NewMongodbBackend error: %v", err)
    }
    if backend.mongoUrl != url {
        t.Fatal("Url name.")
    }
    if backend.database != db {
        t.Fatal("DB not saved.")
    }
}

func TestSaveUser_mongodb(t *testing.T) {
    user2 := UserData{"username2", "email2", []byte("passwordhash2"), "role2"}
    if err := backend.SaveUser(user2); err != nil {
        t.Fatalf("SaveUser mongodb error: %v", err)
    }

    user := UserData{"username", "email", []byte("passwordhash"), "role"}
    if err := backend.SaveUser(user); err != nil {
        t.Fatalf("SaveUser mongodb error: %v", err)
    }
}

func TestNewMongodbAuthBackend_existing(t *testing.T) {
    b2, err := NewMongodbBackend(driverName, driverInfo)
    if err != nil {
        t.Fatalf("NewMongodbBackend (existing) error: %v", err)
    }

    user, ok := b2.User("username")
    if !ok {
        t.Fatal("Secondary backend failed")
    }
    if user.Username != "username" {
        t.Fatal("Username not correct.")
    }
    if user.Email != "email" {
        t.Fatal("User email not correct.")
    }
    if !bytes.Equal(user.Hash, []byte("passwordhash")) {
        t.Fatal("User password not correct.")
    }
}

func TestUser_existing_mongodb(t *testing.T) {
    if user, ok := backend.User("username"); ok {
        if user.Username != "username" {
            t.Fatal("Username not correct.")
        }
        if user.Email != "email" {
            t.Fatal("User email not correct.")
        }
        if !bytes.Equal(user.Hash, []byte("passwordhash")) {
            t.Fatal("User password not correct.")
        }
    } else {
        t.Fatal("User not found")
    }
    if user, ok := backend.User("username2"); ok {
        if user.Username != "username2" {
            t.Fatal("Username not correct.")
        }
        if user.Email != "email2" {
            t.Fatal("User email not correct.")
        }
        if !bytes.Equal(user.Hash, []byte("passwordhash2")) {
            t.Fatal("User password not correct.")
        }
    } else {
        t.Fatal("User not found")
    }
}

func TestUser_notexisting_mongodb(t *testing.T) {
    if _, ok := backend.User("notexist"); ok {
        t.Fatal("Not existing user found.")
    }
}

func TestUsers_mongodb(t *testing.T) {
    var (
        u1 UserData
        u2 UserData
    )
    users := backend.Users()
    if len(users) != 2 {
        t.Fatal("Wrong amount of users found.")
    }
    if users[0].Username == "username" {
        u1 = users[0]
        u2 = users[1]
    } else if users[1].Username == "username" {
        u1 = users[1]
        u2 = users[0]
    } else {
        t.Fatal("One of the users not found.")
    }

    if u1.Username != "username" {
        t.Fatal("Username not correct.")
    }
    if u1.Email != "email" {
        t.Fatal("User email not correct.")
    }
    if !bytes.Equal(u1.Hash, []byte("passwordhash")) {
        t.Fatal("User password not correct.")
    }
    if u2.Username != "username2" {
        t.Fatal("Username not correct.")
    }
    if u2.Email != "email2" {
        t.Fatal("User email not correct.")
    }
    if !bytes.Equal(u2.Hash, []byte("passwordhash2")) {
        t.Fatal("User password not correct.")
    }
}

func TestUpdateUser_mongodb(t *testing.T) {
    user2 := UserData{"username", "newemail", []byte("newpassword"), "newrole"}
    if err := backend.SaveUser(user2); err != nil {
        t.Fatalf("SaveUser mongodb error: %v", err)
    }
    u2, ok := backend.User("username")
    if !ok {
        t.Fatal("Updated user not found")
    }
    if u2.Username != "username" {
        t.Fatal("Username not correct.")
    }
    if u2.Email != "newemail" {
        t.Fatal("User email not correct.")
    }
    if u2.Role != "newrole" {
        t.Fatalf("User role not correct: found %v, expected %v", u2.Role, "newrole");
    }
    if !bytes.Equal(u2.Hash, []byte("newpassword")) {
        t.Fatal("User password not correct.")
    }
}

func TestMongodbDeleteUser_mongodb(t *testing.T) {
    if err := backend.DeleteUser("username"); err != nil {
        t.Fatalf("DeleteUser error: %v", err)
    }
    if err := backend.DeleteUser("username"); err != nil {
        t.Fatalf("DeleteUser error: %v", err)
    }

    if err := backend.DeleteUser("username2"); err != nil {
        t.Fatalf("DeleteUser error: %v", err)
    }
}

<|endoftext|>


<commit_before>package handlers

import (
	"github.com/gin-gonic/gin"
	"github.com/pufferpanel/apufferi/v4"
	"github.com/pufferpanel/apufferi/v4/response"
	"github.com/pufferpanel/apufferi/v4/scope"
	"github.com/pufferpanel/pufferpanel/v2/database"
	"github.com/pufferpanel/pufferpanel/v2/models"
	"github.com/pufferpanel/pufferpanel/v2/services"
	"net/http"
	"strconv"
	"strings"
)

func HasOAuth2Token(c *gin.Context) {
	//if there's a cookie with the token, use that
	cookie, _ := c.Cookie("puffer_auth")
	if cookie != "" {
		c.Request.Header.Set("Authorization", "Bearer "+cookie)
	}

	authHeader := c.Request.Header.Get("Authorization")
	authHeader = strings.TrimSpace(authHeader)

	parts := strings.SplitN(authHeader, " ", 2)
	if len(parts) != 2 {
		c.AbortWithStatus(http.StatusForbidden)
	}

	if parts[0] != "Bearer" || parts[1] == "" {
		c.AbortWithStatus(http.StatusForbidden)
	}

	token, err := services.ParseToken(parts[1])

	if err != nil || !token.Valid {
		c.AbortWithStatus(http.StatusForbidden)
		return
	}

	c.Set("token", token)
	c.Next()
}

func OAuth2Handler(requiredScope scope.Scope, requireServer bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		db, err := database.GetConnection()

		if response.HandleError(c, err, http.StatusInternalServerError) {
		}

		token, ok := c.Get("token")
		if !ok {
			response.HandleError(c, err, http.StatusInternalServerError)
			return
		}
		jwtToken, ok := token.(*apufferi.Token)
		if !ok {
			response.HandleError(c, err, http.StatusInternalServerError)
			return
		}

		ti := jwtToken.Claims

		ss := &services.Server{DB: db}
		us := &services.User{DB: db}

		var serverId string

		var server *models.Server

		i := c.Param("serverId")
		if requireServer {
			server, err = ss.Get(i)
			if err != nil {
				c.AbortWithStatus(http.StatusUnauthorized)
				return
			}
		}

		if requireServer && (server == nil || server.Identifier == "") {
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		} else if requireServer {
			serverId = server.Identifier
		}

		ui, err := strconv.Atoi(ti.Subject)
		if err != nil {
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		user, err := us.GetById(uint(ui))
		if err != nil {
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		allowed := false

		//if this is an audience of oauth2, we can use token directly
		if ti.Audience == "oauth2" {
			scopes := ti.PanelClaims.Scopes[serverId]
			if scopes != nil && apufferi.ContainsScope(scopes, requiredScope) {
				allowed = true
			} else {
				//if there isn't a defined rule, is this user an admin?
				scopes := ti.PanelClaims.Scopes[""]
				if scopes != nil && apufferi.ContainsScope(scopes, scope.ServersAdmin) {
					allowed = true
				}
			}
		} else if ti.Audience == "session" {
			//otherwise, we have to look at what the user has since session based
			ps := &services.Permission{DB: db}
			var perms *models.Permissions
			if serverId == "" {
				perms, err = ps.GetForUserAndServer(user.ID, nil)
			} else {
				perms, err = ps.GetForUserAndServer(user.ID, &serverId)
			}

			if response.HandleError(c, err, http.StatusInternalServerError) {
				return
			}

			if apufferi.ContainsScope(perms.ToScopes(), requiredScope) {
				allowed = true
			} else {
				perms, err = ps.GetForUserAndServer(user.ID, nil)
				if response.HandleError(c, err, http.StatusInternalServerError) {
					return
				}
				if apufferi.ContainsScope(perms.ToScopes(), scope.ServersAdmin) {
					allowed = true
				}
			}
		} else {
			c.AbortWithStatus(http.StatusForbidden)
			return
		}

		if !allowed {
			c.AbortWithStatus(http.StatusForbidden)
			return
		}

		c.Set("server", server)
		c.Set("user", user)
		c.Next()
	}
}
<commit_msg>Abort and return....<commit_after>package handlers

import (
	"github.com/gin-gonic/gin"
	"github.com/pufferpanel/apufferi/v4"
	"github.com/pufferpanel/apufferi/v4/response"
	"github.com/pufferpanel/apufferi/v4/scope"
	"github.com/pufferpanel/pufferpanel/v2/database"
	"github.com/pufferpanel/pufferpanel/v2/models"
	"github.com/pufferpanel/pufferpanel/v2/services"
	"net/http"
	"strconv"
	"strings"
)

func HasOAuth2Token(c *gin.Context) {
	//if there's a cookie with the token, use that
	cookie, _ := c.Cookie("puffer_auth")
	if cookie != "" {
		c.Request.Header.Set("Authorization", "Bearer "+cookie)
	}

	authHeader := c.Request.Header.Get("Authorization")
	authHeader = strings.TrimSpace(authHeader)

	parts := strings.SplitN(authHeader, " ", 2)
	if len(parts) != 2 {
		c.AbortWithStatus(http.StatusForbidden)
		return
	}

	if parts[0] != "Bearer" || parts[1] == "" {
		c.AbortWithStatus(http.StatusForbidden)
		return
	}

	token, err := services.ParseToken(parts[1])

	if err != nil || !token.Valid {
		c.AbortWithStatus(http.StatusForbidden)
		return
	}

	c.Set("token", token)
	c.Next()
}

func OAuth2Handler(requiredScope scope.Scope, requireServer bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		db, err := database.GetConnection()

		if response.HandleError(c, err, http.StatusInternalServerError) {
		}

		token, ok := c.Get("token")
		if !ok {
			response.HandleError(c, err, http.StatusInternalServerError)
			return
		}
		jwtToken, ok := token.(*apufferi.Token)
		if !ok {
			response.HandleError(c, err, http.StatusInternalServerError)
			return
		}

		ti := jwtToken.Claims

		ss := &services.Server{DB: db}
		us := &services.User{DB: db}

		var serverId string

		var server *models.Server

		i := c.Param("serverId")
		if requireServer {
			server, err = ss.Get(i)
			if err != nil {
				c.AbortWithStatus(http.StatusUnauthorized)
				return
			}
		}

		if requireServer && (server == nil || server.Identifier == "") {
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		} else if requireServer {
			serverId = server.Identifier
		}

		ui, err := strconv.Atoi(ti.Subject)
		if err != nil {
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		user, err := us.GetById(uint(ui))
		if err != nil {
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		allowed := false

		//if this is an audience of oauth2, we can use token directly
		if ti.Audience == "oauth2" {
			scopes := ti.PanelClaims.Scopes[serverId]
			if scopes != nil && apufferi.ContainsScope(scopes, requiredScope) {
				allowed = true
			} else {
				//if there isn't a defined rule, is this user an admin?
				scopes := ti.PanelClaims.Scopes[""]
				if scopes != nil && apufferi.ContainsScope(scopes, scope.ServersAdmin) {
					allowed = true
				}
			}
		} else if ti.Audience == "session" {
			//otherwise, we have to look at what the user has since session based
			ps := &services.Permission{DB: db}
			var perms *models.Permissions
			if serverId == "" {
				perms, err = ps.GetForUserAndServer(user.ID, nil)
			} else {
				perms, err = ps.GetForUserAndServer(user.ID, &serverId)
			}

			if response.HandleError(c, err, http.StatusInternalServerError) {
				return
			}

			if apufferi.ContainsScope(perms.ToScopes(), requiredScope) {
				allowed = true
			} else {
				perms, err = ps.GetForUserAndServer(user.ID, nil)
				if response.HandleError(c, err, http.StatusInternalServerError) {
					return
				}
				if apufferi.ContainsScope(perms.ToScopes(), scope.ServersAdmin) {
					allowed = true
				}
			}
		} else {
			c.AbortWithStatus(http.StatusForbidden)
			return
		}

		if !allowed {
			c.AbortWithStatus(http.StatusForbidden)
			return
		}

		c.Set("server", server)
		c.Set("user", user)
		c.Next()
	}
}
<|endoftext|>


<commit_before>package mstate_test

import (
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/charm"
	state "launchpad.net/juju-core/mstate"
	"launchpad.net/juju-core/testing"
	"net/url"
	"sort"
)

// ConnSuite facilitates access to the underlying MongoDB. It is embedded
// in other suites, like StateSuite.
type ConnSuite struct {
	MgoSuite
	session  *mgo.Session
	charms   *mgo.Collection
	machines *mgo.Collection
	services *mgo.Collection
	units    *mgo.Collection
}

func (cs *ConnSuite) SetUpTest(c *C) {
	cs.MgoSuite.SetUpTest(c)
	session, err := mgo.Dial(mgoaddr)
	c.Assert(err, IsNil)
	cs.session = session
	cs.charms = session.DB("juju").C("charms")
	cs.machines = session.DB("juju").C("machines")
	cs.services = session.DB("juju").C("services")
	cs.units = session.DB("juju").C("units")
}

func (cs *ConnSuite) TearDownTest(c *C) {
	cs.session.Close()
	cs.MgoSuite.TearDownTest(c)
}

func (s *ConnSuite) AllMachines(c *C) []string {
	docs := []state.MachineDoc{}
	err := s.machines.Find(bson.D{{"life", state.Alive}}).All(&docs)
	c.Assert(err, IsNil)
	names := []string{}
	for _, v := range docs {
		names = append(names, v.String())
	}
	sort.Strings(names)
	return names
}

type UtilSuite struct {
	MgoSuite
	ConnSuite
	State *state.State
}

func (s *UtilSuite) SetUpTest(c *C) {
	s.ConnSuite.SetUpTest(c)
	st, err := state.Dial(mgoaddr)
	c.Assert(err, IsNil)
	s.State = st
}

func (s *UtilSuite) TearDownTest(c *C) {
	s.State.Close()
	s.ConnSuite.TearDownTest(c)
}

func (s *UtilSuite) AddTestingCharm(c *C, name string) *state.Charm {
	ch := testing.Charms.Dir(name)
	ident := fmt.Sprintf("%s-%d", name, ch.Revision())
	curl := charm.MustParseURL("local:series/" + ident)
	bundleURL, err := url.Parse("http://bundles.example.com/" + ident)
	c.Assert(err, IsNil)
	sch, err := s.State.AddCharm(ch, curl, bundleURL, ident+"-sha256")
	c.Assert(err, IsNil)
	return sch
}
<commit_msg>mstate: document UtilSuite.<commit_after>package mstate_test

import (
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/charm"
	state "launchpad.net/juju-core/mstate"
	"launchpad.net/juju-core/testing"
	"net/url"
	"sort"
)

// ConnSuite facilitates access to the underlying MongoDB.
// It is embedded in UtilSuite.
type ConnSuite struct {
	MgoSuite
	session  *mgo.Session
	charms   *mgo.Collection
	machines *mgo.Collection
	services *mgo.Collection
	units    *mgo.Collection
}

func (cs *ConnSuite) SetUpTest(c *C) {
	cs.MgoSuite.SetUpTest(c)
	session, err := mgo.Dial(mgoaddr)
	c.Assert(err, IsNil)
	cs.session = session
	cs.charms = session.DB("juju").C("charms")
	cs.machines = session.DB("juju").C("machines")
	cs.services = session.DB("juju").C("services")
	cs.units = session.DB("juju").C("units")
}

func (cs *ConnSuite) TearDownTest(c *C) {
	cs.session.Close()
	cs.MgoSuite.TearDownTest(c)
}

func (s *ConnSuite) AllMachines(c *C) []string {
	docs := []state.MachineDoc{}
	err := s.machines.Find(bson.D{{"life", state.Alive}}).All(&docs)
	c.Assert(err, IsNil)
	names := []string{}
	for _, v := range docs {
		names = append(names, v.String())
	}
	sort.Strings(names)
	return names
}

// UtilSuite provides the infrastructure for all other
// test suites (StateSuite, CharmSuite, MachineSuite, etc).
type UtilSuite struct {
	MgoSuite
	ConnSuite
	State *state.State
}

func (s *UtilSuite) SetUpTest(c *C) {
	s.ConnSuite.SetUpTest(c)
	st, err := state.Dial(mgoaddr)
	c.Assert(err, IsNil)
	s.State = st
}

func (s *UtilSuite) TearDownTest(c *C) {
	s.State.Close()
	s.ConnSuite.TearDownTest(c)
}

func (s *UtilSuite) AddTestingCharm(c *C, name string) *state.Charm {
	ch := testing.Charms.Dir(name)
	ident := fmt.Sprintf("%s-%d", name, ch.Revision())
	curl := charm.MustParseURL("local:series/" + ident)
	bundleURL, err := url.Parse("http://bundles.example.com/" + ident)
	c.Assert(err, IsNil)
	sch, err := s.State.AddCharm(ch, curl, bundleURL, ident+"-sha256")
	c.Assert(err, IsNil)
	return sch
}
<|endoftext|>


<commit_before>package mutation

import (
	"github.com/gopherjs/gopherjs/js"
	"honnef.co/go/js/dom"
)

type Observer struct {
	*js.Object
}

func New(f func([]*Record, *Observer)) *Observer {
	return &Observer{js.Global.Get("MutationObserver").New(f)}
}

func (m *Observer) Observe(n dom.Node, i ObserverInit) {
	j := &observerInit{Object: js.Global.Get("Object").Get("constructor").New()}
	j.ChildList = i.ChildList
	j.Attributes = i.Attributes
	j.CharacterData = i.CharacterData
	j.Subtree = i.Subtree
	j.AttributeOldValue = i.AttributeOldValue
	j.CharacterDataOldValue = i.CharacterDataOldValue
	if i.Attributes {
		j.AttributeFilter = i.AttributeFilter
	}
	m.Call("observe", n.Underlying(), j)
}

func (m *Observer) Disconnect() {
	m.Call("disconnect")
}

func (m *Observer) TakeRecords() []*Record {
	o := m.Call("takeRecords")
	l := o.Length()
	mrs := make([]*Record, l)
	for i := 0; i < l; i++ {
		mrs[i] = &Record{Object: o.Index(i)}
	}
	return mrs
}

type Record struct {
	*js.Object
	Type               string     `js:"type"`
	Target             dom.Node   `js:"target"`
	AddedNodes         []dom.Node `js:"addedNodes"`
	RemovedNodes       []dom.Node `js:"removedNodes"`
	PreviousSibling    dom.Node   `js:"previousSibling"`
	NextSibling        dom.Node   `js:"nextSibling"`
	AttributeName      string     `js:"attributeName"`
	AttributeNamespace string     `js:"attributeNamespace"`
	OldValue           string     `js:"oldValue"`
}

type ObserverInit struct {
	ChildList             bool
	Attributes            bool
	CharacterData         bool
	Subtree               bool
	AttributeOldValue     bool
	CharacterDataOldValue bool
	AttributeFilter       []string
}

type observerInit struct {
	*js.Object
	ChildList             bool     `js:"childList"`
	Attributes            bool     `js:"attributes"`
	CharacterData         bool     `js:"characterData"`
	Subtree               bool     `js:"subtree"`
	AttributeOldValue     bool     `js:"attributeOldValue"`
	CharacterDataOldValue bool     `js:"characterDataOldValue"`
	AttributeFilter       []string `js:"attributeFilter"`
}
<commit_msg>converted dom.Node types into method getters<commit_after>package mutation

import (
	"github.com/gopherjs/gopherjs/js"
	"honnef.co/go/js/dom"
)

type Observer struct {
	*js.Object
}

func New(f func([]*Record, *Observer)) *Observer {
	return &Observer{js.Global.Get("MutationObserver").New(f)}
}

func (m *Observer) Observe(n dom.Node, i ObserverInit) {
	j := &observerInit{Object: js.Global.Get("Object").Get("constructor").New()}
	j.ChildList = i.ChildList
	j.Attributes = i.Attributes
	j.CharacterData = i.CharacterData
	j.Subtree = i.Subtree
	j.AttributeOldValue = i.AttributeOldValue
	j.CharacterDataOldValue = i.CharacterDataOldValue
	if i.Attributes {
		j.AttributeFilter = i.AttributeFilter
	}
	m.Call("observe", n.Underlying(), j)
}

func (m *Observer) Disconnect() {
	m.Call("disconnect")
}

func (m *Observer) TakeRecords() []*Record {
	o := m.Call("takeRecords")
	l := o.Length()
	mrs := make([]*Record, l)
	for i := 0; i < l; i++ {
		mrs[i] = &Record{Object: o.Index(i)}
	}
	return mrs
}

type Record struct {
	*js.Object
	Type               string `js:"type"`
	AttributeName      string `js:"attributeName"`
	AttributeNamespace string `js:"attributeNamespace"`
	OldValue           string `js:"oldValue"`
}

func (r *Record) Target() dom.Node {
	return dom.WrapNode(r.Get("target"))
}

func wrapNodes(o *js.Object) []dom.Node {
	l := o.Length()
	toRet := make([]dom.Node, l)
	for i := 0; i < l; i++ {
		toRet[i] = dom.WrapNode(o.Index(i))
	}
	return toRet
}

func (r *Record) AddedNodes() []dom.Node {
	return wrapNodes(r.Get("addedNodes"))
}

func (r *Record) RemovedNodes() []dom.Node {
	return wrapNodes(r.Get("removedNodes"))
}

func (r *Record) PreviousSibling() dom.Node {
	return dom.WrapNode(r.Get("previousSibling"))
}

func (r *Record) NextSibling() dom.Node {
	return dom.WrapNode(r.Get("nextSibling"))
}

type ObserverInit struct {
	ChildList             bool
	Attributes            bool
	CharacterData         bool
	Subtree               bool
	AttributeOldValue     bool
	CharacterDataOldValue bool
	AttributeFilter       []string
}

type observerInit struct {
	*js.Object
	ChildList             bool     `js:"childList"`
	Attributes            bool     `js:"attributes"`
	CharacterData         bool     `js:"characterData"`
	Subtree               bool     `js:"subtree"`
	AttributeOldValue     bool     `js:"attributeOldValue"`
	CharacterDataOldValue bool     `js:"characterDataOldValue"`
	AttributeFilter       []string `js:"attributeFilter"`
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"io"
	"io/ioutil"
	"net/http/httptest"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"
	prefixed "github.com/x-cray/logrus-prefixed-formatter"

	"github.com/TykTechnologies/tyk/apidef"
	"github.com/TykTechnologies/tyk/config"
	logger "github.com/TykTechnologies/tyk/log"
)

func TestJSVMLogs(t *testing.T) {
	var buf bytes.Buffer
	jsvm := JSVM{}
	jsvm.Init(nil)
	jsvm.Log = logrus.New()
	jsvm.Log.Out = &buf
	jsvm.Log.Formatter = new(prefixed.TextFormatter)

	jsvm.RawLog = logrus.New()
	jsvm.RawLog.Out = &buf
	jsvm.RawLog.Formatter = new(logger.RawFormatter)

	const in = `
log("foo")
log('{"x": "y"}')
rawlog("foo")
rawlog('{"x": "y"}')
`
	// note how the logger leaves spaces at the end
	want := []string{
		`time=TIME level=info msg=foo type=log-msg `,
		`time=TIME level=info msg="{\"x\": \"y\"}" type=log-msg `,
		`foo`,
		`{"x": "y"}`,
	}
	if _, err := jsvm.VM.Run(in); err != nil {
		t.Fatalf("failed to run js: %v", err)
	}
	got := strings.Split(strings.Trim(buf.String(), "\n"), "\n")
	i := 0
	timeRe := regexp.MustCompile(`time="[^"]*"`)
	for _, line := range got {
		if i >= len(want) {
			t.Logf("too many lines")
			t.Fail()
			break
		}
		s := timeRe.ReplaceAllString(line, "time=TIME")
		if s != line && !strings.Contains(s, "type=log-msg") {
			continue // log line from elsewhere (async)
		}
		if s != want[i] {
			t.Logf("%s != %s", s, want[i])
			t.Fail()
		}
		i++
	}
}

func TestJSVMBody(t *testing.T) {
	dynMid := &DynamicMiddleware{
		BaseMiddleware: BaseMiddleware{
			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
		},
		MiddlewareClassName: "leakMid",
		Pre:                 true,
	}
	body := "foô \uffff \u0000 \xff bàr"
	req := httptest.NewRequest("GET", "/foo", strings.NewReader(body))
	jsvm := JSVM{}
	jsvm.Init(nil)

	const js = `
var leakMid = new TykJS.TykMiddleware.NewMiddleware({})

leakMid.NewProcessRequest(function(request, session) {
	request.Body += " appended"
	return leakMid.ReturnData(request, session.meta_data)
});`
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm
	dynMid.ProcessRequest(nil, req, nil)

	bs, err := ioutil.ReadAll(req.Body)
	if err != nil {
		t.Fatalf("failed to read final body: %v", err)
	}
	want := body + " appended"
	if got := string(bs); want != got {
		t.Fatalf("JS plugin broke non-UTF8 body %q into %q",
			want, got)
	}
}

func TestJSVMProcessTimeout(t *testing.T) {
	dynMid := &DynamicMiddleware{
		BaseMiddleware: BaseMiddleware{
			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
		},
		MiddlewareClassName: "leakMid",
		Pre:                 true,
	}
	req := httptest.NewRequest("GET", "/foo", strings.NewReader("body"))
	jsvm := JSVM{}
	jsvm.Init(nil)
	jsvm.Timeout = time.Millisecond

	// this js plugin just loops forever, keeping Otto at 100% CPU
	// usage and running forever.
	const js = `
var leakMid = new TykJS.TykMiddleware.NewMiddleware({})

leakMid.NewProcessRequest(function(request, session) {
	while (true) {
	}
	return leakMid.ReturnData(request, session.meta_data)
});`
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	done := make(chan bool)
	go func() {
		dynMid.ProcessRequest(nil, req, nil)
		done <- true
	}()
	select {
	case <-done:
	case <-time.After(time.Second):
		t.Fatal("js vm wasn't killed after its timeout")
	}
}

func TestJSVMConfigData(t *testing.T) {
	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	spec.ConfigData = map[string]interface{}{
		"foo": "bar",
	}
	const js = `
var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({})

testJSVMData.NewProcessRequest(function(request, session, spec) {
	request.SetHeaders["data-foo"] = spec.config_data.foo
	return testJSVMData.ReturnData(request, {})
});`
	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testJSVMData",
		Pre:                 true,
	}
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	r := testReq(t, "GET", "/v1/test-data", nil)
	dynMid.ProcessRequest(nil, r, nil)
	if want, got := "bar", r.Header.Get("data-foo"); want != got {
		t.Fatalf("wanted header to be %q, got %q", want, got)
	}
}

func TestJSVMReturnOverridesFullResponse(t *testing.T) {
	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	spec.ConfigData = map[string]interface{}{
		"foo": "bar",
	}
	const js = `
var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({})

testJSVMData.NewProcessRequest(function(request, session, config) {
	request.ReturnOverrides.ResponseError = "Foobarbaz"
	request.ReturnOverrides.ResponseCode = 200
	request.ReturnOverrides.ResponseHeaders = {
		"X-Foo": "Bar",
		"X-Baz": "Qux"
	}
	return testJSVMData.ReturnData(request, {})
});`
	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testJSVMData",
		Pre:                 true,
	}
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	rec := httptest.NewRecorder()
	r := testReq(t, "GET", "/v1/test-data", nil)
	dynMid.ProcessRequest(rec, r, nil)

	wantBody := "Foobarbaz"
	gotBody := rec.Body.String()
	if wantBody != gotBody {
		t.Fatalf("wanted body to be %q, got %q", wantBody, gotBody)
	}
	if want, got := "Bar", rec.HeaderMap.Get("x-foo"); got != want {
		t.Fatalf("wanted header to be %q, got %q", want, got)
	}
	if want, got := "Qux", rec.HeaderMap.Get("x-baz"); got != want {
		t.Fatalf("wanted header to be %q, got %q", want, got)
	}

	if want := 200; rec.Code != 200 {
		t.Fatalf("wanted code to be %d, got %d", want, rec.Code)
	}
}

func TestJSVMReturnOverridesError(t *testing.T) {
	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	spec.ConfigData = map[string]interface{}{
		"foo": "bar",
	}
	const js = `
var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({})

testJSVMData.NewProcessRequest(function(request, session, config) {
	request.ReturnOverrides.ResponseError = "Foobarbaz"
	request.ReturnOverrides.ResponseCode = 401
	return testJSVMData.ReturnData(request, {})
});`
	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testJSVMData",
		Pre:                 true,
	}
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	r := testReq(t, "GET", "/v1/test-data", nil)
	err, code := dynMid.ProcessRequest(nil, r, nil)

	if want := 401; code != 401 {
		t.Fatalf("wanted code to be %d, got %d", want, code)
	}

	wantBody := "Foobarbaz"
	if !strings.Contains(err.Error(), wantBody) {
		t.Fatalf("wanted body to contain to be %v, got %v", wantBody, err.Error())
	}
}

func TestJSVMUserCore(t *testing.T) {
	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	const js = `
var testJSVMCore = new TykJS.TykMiddleware.NewMiddleware({})

testJSVMCore.NewProcessRequest(function(request, session, config) {
	request.SetHeaders["global"] = globalVar
	return testJSVMCore.ReturnData(request, {})
});`
	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testJSVMCore",
		Pre:                 true,
	}
	tfile, err := ioutil.TempFile("", "tykjs")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := io.WriteString(tfile, `var globalVar = "globalValue"`); err != nil {
		t.Fatal(err)
	}
	globalConf := config.Global()
	old := globalConf.TykJSPath
	globalConf.TykJSPath = tfile.Name()
	config.SetGlobal(globalConf)
	defer func() {
		globalConf.TykJSPath = old
		config.SetGlobal(globalConf)
	}()
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	r := testReq(t, "GET", "/foo", nil)
	dynMid.ProcessRequest(nil, r, nil)

	if want, got := "globalValue", r.Header.Get("global"); want != got {
		t.Fatalf("wanted header to be %q, got %q", want, got)
	}
}
func TestJSVMRequestScheme(t *testing.T) {
	dynMid := &DynamicMiddleware{
		BaseMiddleware: BaseMiddleware{
			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
		},
		MiddlewareClassName: "leakMid",
		Pre:                 true,
	}
	req := httptest.NewRequest("GET", "/foo", nil)
	req.URL.Scheme = "http"
	jsvm := JSVM{}
	jsvm.Init(nil)

	const js = `
var leakMid = new TykJS.TykMiddleware.NewMiddleware({})

leakMid.NewProcessRequest(function(request, session) {
	var test = request.Scheme += " appended"

	var responseObject = {
        Body: test,
        Code: 200
    }
	return leakMid.ReturnData(responseObject, session.meta_data)
});`
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm
	dynMid.ProcessRequest(nil, req, nil)

	bs, err := ioutil.ReadAll(req.Body)
	if err != nil {
		t.Fatalf("failed to read final body: %v", err)
	}
	want := "http" + " appended"
	if got := string(bs); want != got {
		t.Fatalf("JS plugin broke non-UTF8 body %q into %q",
			want, got)
	}
}
<commit_msg>jsvm: add a test for TykMakeHttpRequest<commit_after>package main

import (
	"bytes"
	"io"
	"io/ioutil"
	"net/http/httptest"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"
	prefixed "github.com/x-cray/logrus-prefixed-formatter"

	"github.com/TykTechnologies/tyk/apidef"
	"github.com/TykTechnologies/tyk/config"
	logger "github.com/TykTechnologies/tyk/log"
)

func TestJSVMLogs(t *testing.T) {
	var buf bytes.Buffer
	jsvm := JSVM{}
	jsvm.Init(nil)
	jsvm.Log = logrus.New()
	jsvm.Log.Out = &buf
	jsvm.Log.Formatter = new(prefixed.TextFormatter)

	jsvm.RawLog = logrus.New()
	jsvm.RawLog.Out = &buf
	jsvm.RawLog.Formatter = new(logger.RawFormatter)

	const in = `
log("foo")
log('{"x": "y"}')
rawlog("foo")
rawlog('{"x": "y"}')
`
	// note how the logger leaves spaces at the end
	want := []string{
		`time=TIME level=info msg=foo type=log-msg `,
		`time=TIME level=info msg="{\"x\": \"y\"}" type=log-msg `,
		`foo`,
		`{"x": "y"}`,
	}
	if _, err := jsvm.VM.Run(in); err != nil {
		t.Fatalf("failed to run js: %v", err)
	}
	got := strings.Split(strings.Trim(buf.String(), "\n"), "\n")
	i := 0
	timeRe := regexp.MustCompile(`time="[^"]*"`)
	for _, line := range got {
		if i >= len(want) {
			t.Logf("too many lines")
			t.Fail()
			break
		}
		s := timeRe.ReplaceAllString(line, "time=TIME")
		if s != line && !strings.Contains(s, "type=log-msg") {
			continue // log line from elsewhere (async)
		}
		if s != want[i] {
			t.Logf("%s != %s", s, want[i])
			t.Fail()
		}
		i++
	}
}

func TestJSVMBody(t *testing.T) {
	dynMid := &DynamicMiddleware{
		BaseMiddleware: BaseMiddleware{
			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
		},
		MiddlewareClassName: "leakMid",
		Pre:                 true,
	}
	body := "foô \uffff \u0000 \xff bàr"
	req := httptest.NewRequest("GET", "/foo", strings.NewReader(body))
	jsvm := JSVM{}
	jsvm.Init(nil)

	const js = `
var leakMid = new TykJS.TykMiddleware.NewMiddleware({})

leakMid.NewProcessRequest(function(request, session) {
	request.Body += " appended"
	return leakMid.ReturnData(request, session.meta_data)
});`
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm
	dynMid.ProcessRequest(nil, req, nil)

	bs, err := ioutil.ReadAll(req.Body)
	if err != nil {
		t.Fatalf("failed to read final body: %v", err)
	}
	want := body + " appended"
	if got := string(bs); want != got {
		t.Fatalf("JS plugin broke non-UTF8 body %q into %q",
			want, got)
	}
}

func TestJSVMProcessTimeout(t *testing.T) {
	dynMid := &DynamicMiddleware{
		BaseMiddleware: BaseMiddleware{
			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
		},
		MiddlewareClassName: "leakMid",
		Pre:                 true,
	}
	req := httptest.NewRequest("GET", "/foo", strings.NewReader("body"))
	jsvm := JSVM{}
	jsvm.Init(nil)
	jsvm.Timeout = time.Millisecond

	// this js plugin just loops forever, keeping Otto at 100% CPU
	// usage and running forever.
	const js = `
var leakMid = new TykJS.TykMiddleware.NewMiddleware({})

leakMid.NewProcessRequest(function(request, session) {
	while (true) {
	}
	return leakMid.ReturnData(request, session.meta_data)
});`
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	done := make(chan bool)
	go func() {
		dynMid.ProcessRequest(nil, req, nil)
		done <- true
	}()
	select {
	case <-done:
	case <-time.After(time.Second):
		t.Fatal("js vm wasn't killed after its timeout")
	}
}

func TestJSVMConfigData(t *testing.T) {
	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	spec.ConfigData = map[string]interface{}{
		"foo": "bar",
	}
	const js = `
var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({})

testJSVMData.NewProcessRequest(function(request, session, spec) {
	request.SetHeaders["data-foo"] = spec.config_data.foo
	return testJSVMData.ReturnData(request, {})
});`
	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testJSVMData",
		Pre:                 true,
	}
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	r := testReq(t, "GET", "/v1/test-data", nil)
	dynMid.ProcessRequest(nil, r, nil)
	if want, got := "bar", r.Header.Get("data-foo"); want != got {
		t.Fatalf("wanted header to be %q, got %q", want, got)
	}
}

func TestJSVMReturnOverridesFullResponse(t *testing.T) {
	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	spec.ConfigData = map[string]interface{}{
		"foo": "bar",
	}
	const js = `
var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({})

testJSVMData.NewProcessRequest(function(request, session, config) {
	request.ReturnOverrides.ResponseError = "Foobarbaz"
	request.ReturnOverrides.ResponseCode = 200
	request.ReturnOverrides.ResponseHeaders = {
		"X-Foo": "Bar",
		"X-Baz": "Qux"
	}
	return testJSVMData.ReturnData(request, {})
});`
	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testJSVMData",
		Pre:                 true,
	}
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	rec := httptest.NewRecorder()
	r := testReq(t, "GET", "/v1/test-data", nil)
	dynMid.ProcessRequest(rec, r, nil)

	wantBody := "Foobarbaz"
	gotBody := rec.Body.String()
	if wantBody != gotBody {
		t.Fatalf("wanted body to be %q, got %q", wantBody, gotBody)
	}
	if want, got := "Bar", rec.HeaderMap.Get("x-foo"); got != want {
		t.Fatalf("wanted header to be %q, got %q", want, got)
	}
	if want, got := "Qux", rec.HeaderMap.Get("x-baz"); got != want {
		t.Fatalf("wanted header to be %q, got %q", want, got)
	}

	if want := 200; rec.Code != 200 {
		t.Fatalf("wanted code to be %d, got %d", want, rec.Code)
	}
}

func TestJSVMReturnOverridesError(t *testing.T) {
	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	spec.ConfigData = map[string]interface{}{
		"foo": "bar",
	}
	const js = `
var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({})

testJSVMData.NewProcessRequest(function(request, session, config) {
	request.ReturnOverrides.ResponseError = "Foobarbaz"
	request.ReturnOverrides.ResponseCode = 401
	return testJSVMData.ReturnData(request, {})
});`
	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testJSVMData",
		Pre:                 true,
	}
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	r := testReq(t, "GET", "/v1/test-data", nil)
	err, code := dynMid.ProcessRequest(nil, r, nil)

	if want := 401; code != 401 {
		t.Fatalf("wanted code to be %d, got %d", want, code)
	}

	wantBody := "Foobarbaz"
	if !strings.Contains(err.Error(), wantBody) {
		t.Fatalf("wanted body to contain to be %v, got %v", wantBody, err.Error())
	}
}

func TestJSVMUserCore(t *testing.T) {
	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	const js = `
var testJSVMCore = new TykJS.TykMiddleware.NewMiddleware({})

testJSVMCore.NewProcessRequest(function(request, session, config) {
	request.SetHeaders["global"] = globalVar
	return testJSVMCore.ReturnData(request, {})
});`
	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testJSVMCore",
		Pre:                 true,
	}
	tfile, err := ioutil.TempFile("", "tykjs")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := io.WriteString(tfile, `var globalVar = "globalValue"`); err != nil {
		t.Fatal(err)
	}
	globalConf := config.Global()
	old := globalConf.TykJSPath
	globalConf.TykJSPath = tfile.Name()
	config.SetGlobal(globalConf)
	defer func() {
		globalConf.TykJSPath = old
		config.SetGlobal(globalConf)
	}()
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	r := testReq(t, "GET", "/foo", nil)
	dynMid.ProcessRequest(nil, r, nil)

	if want, got := "globalValue", r.Header.Get("global"); want != got {
		t.Fatalf("wanted header to be %q, got %q", want, got)
	}
}
func TestJSVMRequestScheme(t *testing.T) {
	dynMid := &DynamicMiddleware{
		BaseMiddleware: BaseMiddleware{
			Spec: &APISpec{APIDefinition: &apidef.APIDefinition{}},
		},
		MiddlewareClassName: "leakMid",
		Pre:                 true,
	}
	req := httptest.NewRequest("GET", "/foo", nil)
	req.URL.Scheme = "http"
	jsvm := JSVM{}
	jsvm.Init(nil)

	const js = `
var leakMid = new TykJS.TykMiddleware.NewMiddleware({})
leakMid.NewProcessRequest(function(request, session) {
	var test = request.Scheme += " appended"
	var responseObject = {
        Body: test,
        Code: 200
    }
	return leakMid.ReturnData(responseObject, session.meta_data)
});`
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm
	dynMid.ProcessRequest(nil, req, nil)

	bs, err := ioutil.ReadAll(req.Body)
	if err != nil {
		t.Fatalf("failed to read final body: %v", err)
	}
	want := "http" + " appended"
	if got := string(bs); want != got {
		t.Fatalf("JS plugin broke non-UTF8 body %q into %q",
			want, got)
	}
}

func TestTykMakeHTTPRequest(t *testing.T) {
	ts := newTykTestServer()
	defer ts.Close()

	spec := &APISpec{APIDefinition: &apidef.APIDefinition{}}
	spec.ConfigData = map[string]interface{}{
		"base_url": ts.URL,
	}

	const js = `
	var testTykMakeHTTPRequest = new TykJS.TykMiddleware.NewMiddleware({})

	testTykMakeHTTPRequest.NewProcessRequest(function(request, session, spec) {
		var newRequest = {
			"Method": "GET",
			"Headers": {"Accept": "application/json"},
			"Domain": spec.config_data.base_url,
			"Resource": "/sample"
		}

		var resp = TykMakeHttpRequest(JSON.stringify(newRequest));
		var useableResponse = JSON.parse(resp);

		if(useableResponse.Code > 400) {
			request.ReturnOverrides.ResponseCode = useableResponse.code
			request.ReturnOverrides.ResponseError = "error"
		}

		return testTykMakeHTTPRequest.ReturnData(request, {})
	});`

	dynMid := &DynamicMiddleware{
		BaseMiddleware:      BaseMiddleware{spec, nil},
		MiddlewareClassName: "testTykMakeHTTPRequest",
		Pre:                 true,
	}
	jsvm := JSVM{}
	jsvm.Init(nil)
	if _, err := jsvm.VM.Run(js); err != nil {
		t.Fatalf("failed to set up js plugin: %v", err)
	}
	dynMid.Spec.JSVM = jsvm

	t.Run("Existing endpoint", func(t *testing.T) {
		buildAndLoadAPI(func(spec *APISpec) {
			spec.Proxy.ListenPath = "/sample"
		})

		r := testReq(t, "GET", "/v1/test-data", nil)
		_, code := dynMid.ProcessRequest(nil, r, nil)
		if want := 200; code != 200 {
			t.Fatalf("wanted code to be %d, got %d", want, code)
		}
	})

	t.Run("Nonexistent endpoint", func(t *testing.T) {
		buildAndLoadAPI(func(spec *APISpec) {
			spec.Proxy.ListenPath = "/abc"
		})

		r := testReq(t, "GET", "/v1/test-data", nil)
		_, code := dynMid.ProcessRequest(nil, r, nil)
		if want := 404; code != 404 {
			t.Fatalf("wanted code to be %d, got %d", want, code)
		}
	})
}
<|endoftext|>


<commit_before>package namesys

import (
	"bytes"
	"errors"
	"fmt"
	"time"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
	mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"

	ci "github.com/jbenet/go-ipfs/crypto"
	pb "github.com/jbenet/go-ipfs/namesys/internal/pb"
	routing "github.com/jbenet/go-ipfs/routing"
	u "github.com/jbenet/go-ipfs/util"
)

// ErrExpiredRecord should be returned when an ipns record is
// invalid due to being too old
var ErrExpiredRecord = errors.New("expired record")

// ErrUnrecognizedValidity is returned when an IpnsRecord has an
// unknown validity type.
var ErrUnrecognizedValidity = errors.New("unrecognized validity type")

// ipnsPublisher is capable of publishing and resolving names to the IPFS
// routing system.
type ipnsPublisher struct {
	routing routing.IpfsRouting
}

// NewRoutingPublisher constructs a publisher for the IPFS Routing name system.
func NewRoutingPublisher(route routing.IpfsRouting) Publisher {
	return &ipnsPublisher{routing: route}
}

// Publish implements Publisher. Accepts a keypair and a value,
// and publishes it out to the routing system
func (p *ipnsPublisher) Publish(k ci.PrivKey, value string) error {
	log.Debugf("namesys: Publish %s", value)

	// validate `value` is a ref (multihash)
	_, err := mh.FromB58String(value)
	if err != nil {
		log.Errorf("hash cast failed: %s", value)
		return fmt.Errorf("publish value must be str multihash. %v", err)
	}

	ctx := context.TODO()
	data, err := createRoutingEntryData(k, value)
	if err != nil {
		log.Error("entry creation failed.")
		return err
	}
	pubkey := k.GetPublic()
	pkbytes, err := pubkey.Bytes()
	if err != nil {
		log.Error("pubkey getbytes failed.")
		return err
	}

	nameb := u.Hash(pkbytes)
	namekey := u.Key("/pk/" + string(nameb))

	log.Debugf("Storing pubkey at: %s", namekey)
	// Store associated public key
	timectx, _ := context.WithDeadline(ctx, time.Now().Add(time.Second*4))
	err = p.routing.PutValue(timectx, namekey, pkbytes)
	if err != nil {
		return err
	}

	ipnskey := u.Key("/ipns/" + string(nameb))

	log.Debugf("Storing ipns entry at: %s", ipnskey)
	// Store ipns entry at "/ipns/"+b58(h(pubkey))
	timectx, _ = context.WithDeadline(ctx, time.Now().Add(time.Second*4))
	err = p.routing.PutValue(timectx, ipnskey, data)
	if err != nil {
		return err
	}

	return nil
}

func createRoutingEntryData(pk ci.PrivKey, val string) ([]byte, error) {
	entry := new(pb.IpnsEntry)

	entry.Value = []byte(val)
	typ := pb.IpnsEntry_EOL
	entry.ValidityType = &typ
	entry.Validity = []byte(u.FormatRFC3339(time.Now().Add(time.Hour * 24)))

	sig, err := pk.Sign(ipnsEntryDataForSig(entry))
	if err != nil {
		return nil, err
	}
	entry.Signature = sig
	return proto.Marshal(entry)
}

func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
	return bytes.Join([][]byte{
		e.Value,
		e.Validity,
		[]byte(fmt.Sprint(e.GetValidityType())),
	},
		[]byte{})
}

// ValidateIpnsRecord implements ValidatorFunc and verifies that the
// given 'val' is an IpnsEntry and that that entry is valid.
func ValidateIpnsRecord(k u.Key, val []byte) error {
	entry := new(pb.IpnsEntry)
	err := proto.Unmarshal(val, entry)
	if err != nil {
		return err
	}
	switch entry.GetValidityType() {
	case pb.IpnsEntry_EOL:
		t, err := u.ParseRFC3339(string(entry.GetValue()))
		if err != nil {
			log.Error("Failed parsing time for ipns record EOL")
			return err
		}
		if time.Now().After(t) {
			return ErrExpiredRecord
		}
	default:
		return ErrUnrecognizedValidity
	}
	return nil
}
<commit_msg>Validity time not checked properly<commit_after>package namesys

import (
	"bytes"
	"errors"
	"fmt"
	"time"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
	mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"

	ci "github.com/jbenet/go-ipfs/crypto"
	pb "github.com/jbenet/go-ipfs/namesys/internal/pb"
	routing "github.com/jbenet/go-ipfs/routing"
	u "github.com/jbenet/go-ipfs/util"
)

// ErrExpiredRecord should be returned when an ipns record is
// invalid due to being too old
var ErrExpiredRecord = errors.New("expired record")

// ErrUnrecognizedValidity is returned when an IpnsRecord has an
// unknown validity type.
var ErrUnrecognizedValidity = errors.New("unrecognized validity type")

// ipnsPublisher is capable of publishing and resolving names to the IPFS
// routing system.
type ipnsPublisher struct {
	routing routing.IpfsRouting
}

// NewRoutingPublisher constructs a publisher for the IPFS Routing name system.
func NewRoutingPublisher(route routing.IpfsRouting) Publisher {
	return &ipnsPublisher{routing: route}
}

// Publish implements Publisher. Accepts a keypair and a value,
// and publishes it out to the routing system
func (p *ipnsPublisher) Publish(k ci.PrivKey, value string) error {
	log.Debugf("namesys: Publish %s", value)

	// validate `value` is a ref (multihash)
	_, err := mh.FromB58String(value)
	if err != nil {
		log.Errorf("hash cast failed: %s", value)
		return fmt.Errorf("publish value must be str multihash. %v", err)
	}

	ctx := context.TODO()
	data, err := createRoutingEntryData(k, value)
	if err != nil {
		log.Error("entry creation failed.")
		return err
	}
	pubkey := k.GetPublic()
	pkbytes, err := pubkey.Bytes()
	if err != nil {
		log.Error("pubkey getbytes failed.")
		return err
	}

	nameb := u.Hash(pkbytes)
	namekey := u.Key("/pk/" + string(nameb))

	log.Debugf("Storing pubkey at: %s", namekey)
	// Store associated public key
	timectx, _ := context.WithDeadline(ctx, time.Now().Add(time.Second*4))
	err = p.routing.PutValue(timectx, namekey, pkbytes)
	if err != nil {
		return err
	}

	ipnskey := u.Key("/ipns/" + string(nameb))

	log.Debugf("Storing ipns entry at: %s", ipnskey)
	// Store ipns entry at "/ipns/"+b58(h(pubkey))
	timectx, _ = context.WithDeadline(ctx, time.Now().Add(time.Second*4))
	err = p.routing.PutValue(timectx, ipnskey, data)
	if err != nil {
		return err
	}

	return nil
}

func createRoutingEntryData(pk ci.PrivKey, val string) ([]byte, error) {
	entry := new(pb.IpnsEntry)

	entry.Value = []byte(val)
	typ := pb.IpnsEntry_EOL
	entry.ValidityType = &typ
	entry.Validity = []byte(u.FormatRFC3339(time.Now().Add(time.Hour * 24)))

	sig, err := pk.Sign(ipnsEntryDataForSig(entry))
	if err != nil {
		return nil, err
	}
	entry.Signature = sig
	return proto.Marshal(entry)
}

func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
	return bytes.Join([][]byte{
		e.Value,
		e.Validity,
		[]byte(fmt.Sprint(e.GetValidityType())),
	},
		[]byte{})
}

// ValidateIpnsRecord implements ValidatorFunc and verifies that the
// given 'val' is an IpnsEntry and that that entry is valid.
func ValidateIpnsRecord(k u.Key, val []byte) error {
	entry := new(pb.IpnsEntry)
	err := proto.Unmarshal(val, entry)
	if err != nil {
		return err
	}
	switch entry.GetValidityType() {
	case pb.IpnsEntry_EOL:
		t, err := u.ParseRFC3339(string(entry.GetValidity()))
		if err != nil {
			log.Error("Failed parsing time for ipns record EOL")
			return err
		}
		if time.Now().After(t) {
			return ErrExpiredRecord
		}
	default:
		return ErrUnrecognizedValidity
	}
	return nil
}
<|endoftext|>