<commit_before>package backends

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/schachmat/wego/iface"
)

type forecastConfig struct {
	apiKey string
	lang   string
	debug  bool
	tz     *time.Location
}

type forecastDataPoint struct {
	Time                *float64 `json:"time"`
	Summary             string   `json:"summary"`
	Icon                string   `json:"icon"`
	SunriseTime         *float32 `json:"sunriseTime"`
	SunsetTime          *float32 `json:"sunsetTime"`
	PrecipIntensity     *float32 `json:"precipIntensity"`
	PrecipProbability   *float32 `json:"precipProbability"`
	Temperature         *float32 `json:"temperature"`
	TemperatureMin      *float32 `json:"temperatureMin"`
	TemperatureMax      *float32 `json:"temperatureMax"`
	ApparentTemperature *float32 `json:"apparentTemperature"`
	WindSpeed           *float32 `json:"windSpeed"`
	WindBearing         *float32 `json:"windBearing"`
	Visibility          *float32 `json:"visibility"`
}

type forecastDataBlock struct {
	Summary string              `json:"summary"`
	Icon    string              `json:"icon"`
	Data    []forecastDataPoint `json:"data"`
}

type forecastResponse struct {
	Latitude  *float32          `json:"latitude"`
	Longitude *float32          `json:"longitude"`
	Timezone  *string           `json:"timezone"`
	Currently forecastDataPoint `json:"currently"`
	Hourly    forecastDataBlock `json:"hourly"`
}

const (
	// see https://developer.forecast.io/docs/v2
	// see also https://github.com/mlbright/forecast
	//https://api.forecast.io/forecast/APIKEY/LATITUDE,LONGITUDE
	forecastWuri = "https://api.forecast.io/forecast/%s/%s?units=ca&lang=%s&exclude=minutely,daily,alerts,flags&extend=hourly"
)

func (c *forecastConfig) ParseDaily(db forecastDataBlock, numdays int) []iface.Day {
	var forecast []iface.Day
	var day *iface.Day

	for _, dp := range db.Data {
		slot, err := c.parseCond(dp)
		if err != nil {
			log.Println("Error parsing hourly weather condition:", err)
			continue
		}

		if day != nil && day.Date.Day() != slot.Time.Day() {
			if len(forecast) >= numdays-1 {
				break
			}
			forecast = append(forecast, *day)
			day = nil
		}
		if day == nil {
			day = new(iface.Day)
			day.Date = slot.Time
			//TODO: min-,max-temperature, astronomy
		}

		day.Slots = append(day.Slots, slot)
	}
	return append(forecast, *day)
}

func (c *forecastConfig) parseCond(dp forecastDataPoint) (ret iface.Cond, err error) {
	codemap := map[string]iface.WeatherCode{
		"clear-day":           iface.CodeSunny,
		"clear-night":         iface.CodeSunny,
		"rain":                iface.CodeLightRain,
		"snow":                iface.CodeLightSnow,
		"sleet":               iface.CodeLightSleet,
		"wind":                iface.CodePartlyCloudy,
		"fog":                 iface.CodeFog,
		"cloudy":              iface.CodeCloudy,
		"partly-cloudy-day":   iface.CodePartlyCloudy,
		"partly-cloudy-night": iface.CodePartlyCloudy,
		"thunderstorm":        iface.CodeThunderyShowers,
	}

	if dp.Time == nil {
		return iface.Cond{}, fmt.Errorf("The forecast.io response did not provide a time for the weather condition")
	}
	ret.Time = time.Unix(int64(*dp.Time), 0).In(c.tz)

	ret.Code = iface.CodeUnknown
	if val, ok := codemap[dp.Icon]; ok {
		ret.Code = val
	}
	ret.Desc = dp.Summary

	ret.TempC = dp.Temperature
	ret.FeelsLikeC = dp.ApparentTemperature

	if dp.PrecipProbability != nil {
		var p int = int(*dp.PrecipProbability * 100)
		ret.ChanceOfRainPercent = &p
	}

	if dp.PrecipIntensity != nil && *dp.PrecipIntensity >= 0 {
		var p float32 = *dp.PrecipIntensity / 1000
		ret.PrecipM = &p
	}

	if dp.Visibility != nil && *dp.Visibility >= 0 {
		var p float32 = *dp.Visibility * 1000
		ret.VisibleDistM = &p
	}

	if dp.WindSpeed != nil && *dp.WindSpeed >= 0 {
		ret.WindspeedKmph = dp.WindSpeed
	}

	//ret.WindGustKmph not provided by forecast.io :(

	if dp.WindBearing != nil && *dp.WindBearing >= 0 {
		var p int = int(*dp.WindBearing) % 360
		ret.WinddirDegree = &p
	}

	return ret, nil
}

func (c *forecastConfig) fetch(url string) (*forecastResponse, error) {
	res, err := http.Get(url)
	if err != nil {
		return nil, fmt.Errorf("Unable to get (%s): %v", url, err)
	} else if res.StatusCode != 200 {
		return nil, fmt.Errorf("Unable to get (%s): http status %d", url, res.StatusCode)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err)
	}

	if c.debug {
		log.Printf("Response (%s): %s\n", url, string(body))
	}

	var resp forecastResponse
	if err = json.Unmarshal(body, &resp); err != nil {
		return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body))
	}

	if resp.Timezone == nil {
		log.Printf("No timezone set in response (%s)", url)
	} else {
		c.tz, err = time.LoadLocation(*resp.Timezone)
		if err != nil {
			log.Printf("Unknown Timezone used in response (%s)", url)
		}
	}
	return &resp, nil
}

func (c *forecastConfig) fetchToday(location string) ([]iface.Cond, error) {
	location = fmt.Sprintf("%s,%d", location, time.Now().Unix())

	resp, err := c.fetch(fmt.Sprintf(forecastWuri, c.apiKey, location, c.lang))
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch todays weather data: %v\n", err)
	}

	days := c.ParseDaily(resp.Hourly, 1)
	if len(days) < 1 {
		return nil, fmt.Errorf("Failed to parse today\n")
	}
	return days[0].Slots, nil
}

func (c *forecastConfig) Setup() {
	flag.StringVar(&c.apiKey, "forecast-api-key", "", "forecast backend: the api `KEY` to use")
	flag.StringVar(&c.lang, "forecast-lang", "en", "forecast backend: the `LANGUAGE` to request from forecast.io")
	flag.BoolVar(&c.debug, "forecast-debug", false, "forecast backend: print raw requests and responses")
}

func (c *forecastConfig) Fetch(location string, numdays int) iface.Data {
	var ret iface.Data
	todayChan := make(chan []iface.Cond)

	if len(c.apiKey) == 0 {
		log.Fatal("No forecast.io API key specified.\nYou have to register for one at https://developer.forecast.io/register")
	}

	c.tz = time.Local

	go func() {
		slots, err := c.fetchToday(location)
		if err != nil {
			log.Fatal("Failed to fetch todays weather data: %v\n", err)
		}
		todayChan <- slots
	}()

	resp, err := c.fetch(fmt.Sprintf(forecastWuri, c.apiKey, location, c.lang))
	if err != nil {
		log.Fatalf("Failed to fetch weather data: %v\n", err)
	}

	if resp.Latitude == nil || resp.Longitude == nil {
		log.Println("nil response for latitude,longitude")
		ret.Location = location
	} else {
		ret.GeoLoc = &iface.LatLon{Latitude: *resp.Latitude, Longitude: *resp.Longitude}
		ret.Location = fmt.Sprintf("%f:%f", *resp.Latitude, *resp.Longitude)
	}

	if ret.Current, err = c.parseCond(resp.Currently); err != nil {
		log.Fatalf("Could not parse current weather condition: %v", err)
	}
	ret.Forecast = c.ParseDaily(resp.Hourly, numdays)

	if numdays >= 1 {
		var tHistory, tFuture = <-todayChan, ret.Forecast[0].Slots
		var tRet []iface.Cond
		h, f := 0, 0

		// merge forecast and history from current day
		for h < len(tHistory) || f < len(tFuture) {
			if f >= len(tFuture) {
				tRet = append(tRet, tHistory[h])
				h++
			} else if h >= len(tHistory) || tHistory[h].Time.After(tFuture[f].Time) {
				tRet = append(tRet, tFuture[f])
				f++
			} else if tHistory[h].Time.Before(tFuture[f].Time) {
				tRet = append(tRet, tHistory[h])
				h++
			} else {
				tRet = append(tRet, tFuture[f])
				h++
				f++
			}
		}
		ret.Forecast[0].Slots = tRet
	}
	return ret
}

func init() {
	iface.AllBackends["forecast.io"] = &forecastConfig{}
}
<commit_msg>forecast.io: check location == lat,lon<commit_after>package backends

import (
	"regexp"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/schachmat/wego/iface"
)

type forecastConfig struct {
	apiKey string
	lang   string
	debug  bool
	tz     *time.Location
}

type forecastDataPoint struct {
	Time                *float64 `json:"time"`
	Summary             string   `json:"summary"`
	Icon                string   `json:"icon"`
	SunriseTime         *float32 `json:"sunriseTime"`
	SunsetTime          *float32 `json:"sunsetTime"`
	PrecipIntensity     *float32 `json:"precipIntensity"`
	PrecipProbability   *float32 `json:"precipProbability"`
	Temperature         *float32 `json:"temperature"`
	TemperatureMin      *float32 `json:"temperatureMin"`
	TemperatureMax      *float32 `json:"temperatureMax"`
	ApparentTemperature *float32 `json:"apparentTemperature"`
	WindSpeed           *float32 `json:"windSpeed"`
	WindBearing         *float32 `json:"windBearing"`
	Visibility          *float32 `json:"visibility"`
}

type forecastDataBlock struct {
	Summary string              `json:"summary"`
	Icon    string              `json:"icon"`
	Data    []forecastDataPoint `json:"data"`
}

type forecastResponse struct {
	Latitude  *float32          `json:"latitude"`
	Longitude *float32          `json:"longitude"`
	Timezone  *string           `json:"timezone"`
	Currently forecastDataPoint `json:"currently"`
	Hourly    forecastDataBlock `json:"hourly"`
}

const (
	// see https://developer.forecast.io/docs/v2
	// see also https://github.com/mlbright/forecast
	//https://api.forecast.io/forecast/APIKEY/LATITUDE,LONGITUDE
	forecastWuri = "https://api.forecast.io/forecast/%s/%s?units=ca&lang=%s&exclude=minutely,daily,alerts,flags&extend=hourly"
)

func (c *forecastConfig) ParseDaily(db forecastDataBlock, numdays int) []iface.Day {
	var forecast []iface.Day
	var day *iface.Day

	for _, dp := range db.Data {
		slot, err := c.parseCond(dp)
		if err != nil {
			log.Println("Error parsing hourly weather condition:", err)
			continue
		}

		if day != nil && day.Date.Day() != slot.Time.Day() {
			if len(forecast) >= numdays-1 {
				break
			}
			forecast = append(forecast, *day)
			day = nil
		}
		if day == nil {
			day = new(iface.Day)
			day.Date = slot.Time
			//TODO: min-,max-temperature, astronomy
		}

		day.Slots = append(day.Slots, slot)
	}
	return append(forecast, *day)
}

func (c *forecastConfig) parseCond(dp forecastDataPoint) (ret iface.Cond, err error) {
	codemap := map[string]iface.WeatherCode{
		"clear-day":           iface.CodeSunny,
		"clear-night":         iface.CodeSunny,
		"rain":                iface.CodeLightRain,
		"snow":                iface.CodeLightSnow,
		"sleet":               iface.CodeLightSleet,
		"wind":                iface.CodePartlyCloudy,
		"fog":                 iface.CodeFog,
		"cloudy":              iface.CodeCloudy,
		"partly-cloudy-day":   iface.CodePartlyCloudy,
		"partly-cloudy-night": iface.CodePartlyCloudy,
		"thunderstorm":        iface.CodeThunderyShowers,
	}

	if dp.Time == nil {
		return iface.Cond{}, fmt.Errorf("The forecast.io response did not provide a time for the weather condition")
	}
	ret.Time = time.Unix(int64(*dp.Time), 0).In(c.tz)

	ret.Code = iface.CodeUnknown
	if val, ok := codemap[dp.Icon]; ok {
		ret.Code = val
	}
	ret.Desc = dp.Summary

	ret.TempC = dp.Temperature
	ret.FeelsLikeC = dp.ApparentTemperature

	if dp.PrecipProbability != nil {
		var p int = int(*dp.PrecipProbability * 100)
		ret.ChanceOfRainPercent = &p
	}

	if dp.PrecipIntensity != nil && *dp.PrecipIntensity >= 0 {
		var p float32 = *dp.PrecipIntensity / 1000
		ret.PrecipM = &p
	}

	if dp.Visibility != nil && *dp.Visibility >= 0 {
		var p float32 = *dp.Visibility * 1000
		ret.VisibleDistM = &p
	}

	if dp.WindSpeed != nil && *dp.WindSpeed >= 0 {
		ret.WindspeedKmph = dp.WindSpeed
	}

	//ret.WindGustKmph not provided by forecast.io :(

	if dp.WindBearing != nil && *dp.WindBearing >= 0 {
		var p int = int(*dp.WindBearing) % 360
		ret.WinddirDegree = &p
	}

	return ret, nil
}

func (c *forecastConfig) fetch(url string) (*forecastResponse, error) {
	res, err := http.Get(url)
	if err != nil {
		return nil, fmt.Errorf("Unable to get (%s): %v", url, err)
	} else if res.StatusCode != 200 {
		return nil, fmt.Errorf("Unable to get (%s): http status %d", url, res.StatusCode)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err)
	}

	if c.debug {
		log.Printf("Response (%s): %s\n", url, string(body))
	}

	var resp forecastResponse
	if err = json.Unmarshal(body, &resp); err != nil {
		return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body))
	}

	if resp.Timezone == nil {
		log.Printf("No timezone set in response (%s)", url)
	} else {
		c.tz, err = time.LoadLocation(*resp.Timezone)
		if err != nil {
			log.Printf("Unknown Timezone used in response (%s)", url)
		}
	}
	return &resp, nil
}

func (c *forecastConfig) fetchToday(location string) ([]iface.Cond, error) {
	location = fmt.Sprintf("%s,%d", location, time.Now().Unix())

	resp, err := c.fetch(fmt.Sprintf(forecastWuri, c.apiKey, location, c.lang))
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch todays weather data: %v\n", err)
	}

	days := c.ParseDaily(resp.Hourly, 1)
	if len(days) < 1 {
		return nil, fmt.Errorf("Failed to parse today\n")
	}
	return days[0].Slots, nil
}

func (c *forecastConfig) Setup() {
	flag.StringVar(&c.apiKey, "forecast-api-key", "", "forecast backend: the api `KEY` to use")
	flag.StringVar(&c.lang, "forecast-lang", "en", "forecast backend: the `LANGUAGE` to request from forecast.io")
	flag.BoolVar(&c.debug, "forecast-debug", false, "forecast backend: print raw requests and responses")
}

func (c *forecastConfig) Fetch(location string, numdays int) iface.Data {
	var ret iface.Data
	todayChan := make(chan []iface.Cond)

	if len(c.apiKey) == 0 {
		log.Fatal("No forecast.io API key specified.\nYou have to register for one at https://developer.forecast.io/register")
	}
	if matched, err := regexp.MatchString(`^-?[0-9]*(\.[0-9]+)?,-?[0-9]*(\.[0-9]+)?$`, location); !matched || err != nil {
		log.Fatalf("Error: The forecast.io backend only supports latitude,longitude pairs as location.\nTry `40.748,-73.985` instead of `%s` to get weather for New York", location)
	}

	c.tz = time.Local

	go func() {
		slots, err := c.fetchToday(location)
		if err != nil {
			log.Fatal("Failed to fetch todays weather data: %v\n", err)
		}
		todayChan <- slots
	}()

	resp, err := c.fetch(fmt.Sprintf(forecastWuri, c.apiKey, location, c.lang))
	if err != nil {
		log.Fatalf("Failed to fetch weather data: %v\n", err)
	}

	if resp.Latitude == nil || resp.Longitude == nil {
		log.Println("nil response for latitude,longitude")
		ret.Location = location
	} else {
		ret.GeoLoc = &iface.LatLon{Latitude: *resp.Latitude, Longitude: *resp.Longitude}
		ret.Location = fmt.Sprintf("%f:%f", *resp.Latitude, *resp.Longitude)
	}

	if ret.Current, err = c.parseCond(resp.Currently); err != nil {
		log.Fatalf("Could not parse current weather condition: %v", err)
	}
	ret.Forecast = c.ParseDaily(resp.Hourly, numdays)

	if numdays >= 1 {
		var tHistory, tFuture = <-todayChan, ret.Forecast[0].Slots
		var tRet []iface.Cond
		h, f := 0, 0

		// merge forecast and history from current day
		for h < len(tHistory) || f < len(tFuture) {
			if f >= len(tFuture) {
				tRet = append(tRet, tHistory[h])
				h++
			} else if h >= len(tHistory) || tHistory[h].Time.After(tFuture[f].Time) {
				tRet = append(tRet, tFuture[f])
				f++
			} else if tHistory[h].Time.Before(tFuture[f].Time) {
				tRet = append(tRet, tHistory[h])
				h++
			} else {
				tRet = append(tRet, tFuture[f])
				h++
				f++
			}
		}
		ret.Forecast[0].Slots = tRet
	}
	return ret
}

func init() {
	iface.AllBackends["forecast.io"] = &forecastConfig{}
}
<|endoftext|>


<commit_before>// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package middlewares

import (
	"github.com/goharbor/harbor/src/lib"
	"net/http"
	"regexp"

	"github.com/astaxie/beego"
	"github.com/goharbor/harbor/src/pkg/distribution"
	"github.com/goharbor/harbor/src/server/middleware"
	"github.com/goharbor/harbor/src/server/middleware/artifactinfo"
	"github.com/goharbor/harbor/src/server/middleware/csrf"
	"github.com/goharbor/harbor/src/server/middleware/log"
	"github.com/goharbor/harbor/src/server/middleware/metric"
	"github.com/goharbor/harbor/src/server/middleware/notification"
	"github.com/goharbor/harbor/src/server/middleware/orm"
	"github.com/goharbor/harbor/src/server/middleware/readonly"
	"github.com/goharbor/harbor/src/server/middleware/requestid"
	"github.com/goharbor/harbor/src/server/middleware/security"
	"github.com/goharbor/harbor/src/server/middleware/session"
	"github.com/goharbor/harbor/src/server/middleware/transaction"
)

var (
	match         = regexp.MustCompile
	numericRegexp = match(`[0-9]+`)

	// The ping endpoint will be blocked when DB conns reach the max open conns of the sql.DB
	// which will make ping request timeout, so skip the middlewares which will require DB conn.
	pingSkipper = middleware.MethodAndPathSkipper(http.MethodGet, match("^/api/v2.0/ping"))

	// dbTxSkippers skip the transaction middleware for GET Blob, PATCH Blob Upload and PUT Blob Upload APIs
	// because the APIs may take a long time to run, enable the transaction middleware in them will hold the database connections
	// until the API finished, this behavior may eat all the database connections.
	// There are no database writing operations in the GET Blob and PATCH Blob APIs, so skip the transaction middleware is all ok.
	// For the PUT Blob Upload API, we will make a transaction manually to write blob info to the database when put blob upload successfully.
	dbTxSkippers = []middleware.Skipper{
		middleware.MethodAndPathSkipper(http.MethodGet, distribution.BlobURLRegexp),
		middleware.MethodAndPathSkipper(http.MethodPatch, distribution.BlobUploadURLRegexp),
		middleware.MethodAndPathSkipper(http.MethodPut, distribution.BlobUploadURLRegexp),
		middleware.MethodAndPathSkipper(http.MethodGet, lib.V2CatalogURLRe),
		pingSkipper,
	}

	// readonlySkippers skip the post request when harbor sets to readonly.
	readonlySkippers = []middleware.Skipper{
		middleware.MethodAndPathSkipper(http.MethodPut, match("^/api/v2.0/configurations")),
		middleware.MethodAndPathSkipper(http.MethodPut, match("^/api/internal/configurations")),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/login")),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/userExists")),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/oidc/onboard")),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/adminjob/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/replication/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/replication/task/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/webhook/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/retention/task/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/schedules/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/webhook/"+numericRegexp.String())),
		pingSkipper,
	}
)

// MiddleWares returns global middlewares
func MiddleWares() []beego.MiddleWare {
	return []beego.MiddleWare{
		metric.Middleware(),
		requestid.Middleware(),
		log.Middleware(),
		session.Middleware(),
		csrf.Middleware(),
		orm.Middleware(pingSkipper),
		notification.Middleware(pingSkipper), // notification must ahead of transaction ensure the DB transaction execution complete
		transaction.Middleware(dbTxSkippers...),
		artifactinfo.Middleware(),
		security.Middleware(pingSkipper),
		security.UnauthorizedMiddleware(),
		readonly.Middleware(readonlySkippers...),
	}
}
<commit_msg>perf: skip db tx for get, head and options api requests (#14837)<commit_after>// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package middlewares

import (
	"net/http"
	"regexp"

	"github.com/astaxie/beego"
	"github.com/goharbor/harbor/src/pkg/distribution"
	"github.com/goharbor/harbor/src/server/middleware"
	"github.com/goharbor/harbor/src/server/middleware/artifactinfo"
	"github.com/goharbor/harbor/src/server/middleware/csrf"
	"github.com/goharbor/harbor/src/server/middleware/log"
	"github.com/goharbor/harbor/src/server/middleware/metric"
	"github.com/goharbor/harbor/src/server/middleware/notification"
	"github.com/goharbor/harbor/src/server/middleware/orm"
	"github.com/goharbor/harbor/src/server/middleware/readonly"
	"github.com/goharbor/harbor/src/server/middleware/requestid"
	"github.com/goharbor/harbor/src/server/middleware/security"
	"github.com/goharbor/harbor/src/server/middleware/session"
	"github.com/goharbor/harbor/src/server/middleware/transaction"
)

var (
	match         = regexp.MustCompile
	numericRegexp = match(`[0-9]+`)

	// The ping endpoint will be blocked when DB conns reach the max open conns of the sql.DB
	// which will make ping request timeout, so skip the middlewares which will require DB conn.
	pingSkipper = middleware.MethodAndPathSkipper(http.MethodGet, match("^/api/v2.0/ping"))

	// dbTxSkippers skip the transaction middleware for PATCH Blob Upload, PUT Blob Upload and `Read` APIs
	// because the APIs may take a long time to run, enable the transaction middleware in them will hold the database connections
	// until the API finished, this behavior may eat all the database connections.
	// There are no database writing operations in the PATCH Blob APIs, so skip the transaction middleware is all ok.
	// For the PUT Blob Upload API, we will make a transaction manually to write blob info to the database when put blob upload successfully.
	dbTxSkippers = []middleware.Skipper{
		middleware.MethodAndPathSkipper(http.MethodPatch, distribution.BlobUploadURLRegexp),
		middleware.MethodAndPathSkipper(http.MethodPut, distribution.BlobUploadURLRegexp),
		func(r *http.Request) bool { // skip tx for GET, HEAD and Options requests
			m := r.Method
			return m == http.MethodGet || m == http.MethodHead || m == http.MethodOptions
		},
	}

	// readonlySkippers skip the post request when harbor sets to readonly.
	readonlySkippers = []middleware.Skipper{
		middleware.MethodAndPathSkipper(http.MethodPut, match("^/api/v2.0/configurations")),
		middleware.MethodAndPathSkipper(http.MethodPut, match("^/api/internal/configurations")),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/login")),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/userExists")),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/oidc/onboard")),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/adminjob/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/replication/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/replication/task/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/webhook/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/retention/task/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/schedules/"+numericRegexp.String())),
		middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/webhook/"+numericRegexp.String())),
		pingSkipper,
	}
)

// MiddleWares returns global middlewares
func MiddleWares() []beego.MiddleWare {
	return []beego.MiddleWare{
		metric.Middleware(),
		requestid.Middleware(),
		log.Middleware(),
		session.Middleware(),
		csrf.Middleware(),
		orm.Middleware(pingSkipper),
		notification.Middleware(pingSkipper), // notification must ahead of transaction ensure the DB transaction execution complete
		transaction.Middleware(dbTxSkippers...),
		artifactinfo.Middleware(),
		security.Middleware(pingSkipper),
		security.UnauthorizedMiddleware(),
		readonly.Middleware(readonlySkippers...),
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"

func second() {
	fmt.Println("second.begin")

	defer func() {
		fmt.Println("second.defer")

		if error := recover(); error != nil {
			fmt.Println("second.recover:", error)
			panic(error)
		}
	}()

	panic("Something goes wrong((")

	fmt.Println("second.end")
}

func first() {
	fmt.Println("first.begin")

	defer func() {
		fmt.Println("first.defer")

		if error := recover(); error != nil {
			fmt.Println("first.recover :", error)
			panic(error)
		}
	}()

	second()

	fmt.Println("first.end")
}

func main() {
	fmt.Println("main.begin")

	defer func() {
		fmt.Println("main.defer")

		if error := recover(); error != nil {
			fmt.Println("main.recover  :", error)
		}
	}()

	first()

	fmt.Println("main.end")
}
<commit_msg>fix lesson_18_function_panic_recover<commit_after>package main

import "fmt"

func second() {
	fmt.Println("second.begin")

	defer func() {
		fmt.Println("second.defer")

		if error := recover(); error != nil {
			fmt.Println("second.recover:", error)
			panic(error)
		}
	}()

	panic("Something went wrong((")

	fmt.Println("second.end")
}

func first() {
	fmt.Println("first.begin")

	defer func() {
		fmt.Println("first.defer")

		if error := recover(); error != nil {
			fmt.Println("first.recover :", error)
			panic(error)
		}
	}()

	second()

	fmt.Println("first.end")
}

func main() {
	fmt.Println("main.begin")

	defer func() {
		fmt.Println("main.defer")

		if error := recover(); error != nil {
			fmt.Println("main.recover  :", error)
		}
	}()

	first()

	fmt.Println("main.end")
}
<|endoftext|>


<commit_before>package backends

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"time"

	"github.com/schachmat/wego/iface"
)

type forecastConfig struct {
	apiKey string
	lang   string
	debug  bool
	tz     *time.Location
}

type forecastDataPoint struct {
	Time                *int64   `json:"time"`
	Summary             string   `json:"summary"`
	Icon                string   `json:"icon"`
	SunriseTime         *int64   `json:"sunriseTime"`
	SunsetTime          *int64   `json:"sunsetTime"`
	PrecipIntensity     *float32 `json:"precipIntensity"`
	PrecipProb          *float32 `json:"precipProbability"`
	Temperature         *float32 `json:"temperature"`
	ApparentTemperature *float32 `json:"apparentTemperature"`
	WindSpeed           *float32 `json:"windSpeed"`
	WindBearing         *float32 `json:"windBearing"`
	Visibility          *float32 `json:"visibility"`
	Humidity            *float32 `json:"humidity"`
}

type forecastDataBlock struct {
	Summary string              `json:"summary"`
	Icon    string              `json:"icon"`
	Data    []forecastDataPoint `json:"data"`
}

type forecastResponse struct {
	Latitude  *float32          `json:"latitude"`
	Longitude *float32          `json:"longitude"`
	Timezone  *string           `json:"timezone"`
	Currently forecastDataPoint `json:"currently"`
	Hourly    forecastDataBlock `json:"hourly"`
	Daily     forecastDataBlock `json:"daily"`
}

const (
	// see https://developer.forecast.io/docs/v2
	// see also https://github.com/mlbright/forecast
	//https://api.forecast.io/forecast/APIKEY/LATITUDE,LONGITUDE
	forecastWuri = "https://api.forecast.io/forecast/%s/%s?units=ca&lang=%s&exclude=minutely,alerts,flags&extend=hourly"
)

func (c *forecastConfig) parseAstro(cur *iface.Day, days []forecastDataPoint) {
	for _, day := range days {
		if day.Time != nil && cur.Date.Day() == time.Unix(*day.Time, 0).In(c.tz).Day() {
			if day.SunriseTime != nil {
				cur.Astronomy.Sunrise = time.Unix(*day.SunriseTime, 0).In(c.tz)
			}
			if day.SunsetTime != nil {
				cur.Astronomy.Sunset = time.Unix(*day.SunsetTime, 0).In(c.tz)
			}
			return
		}
	}
}

func (c *forecastConfig) parseDaily(hours, days forecastDataBlock, numdays int) []iface.Day {
	var forecast []iface.Day
	var day *iface.Day

	for _, hourData := range hours.Data {
		slot, err := c.parseCond(hourData)
		if err != nil {
			log.Println("Error parsing hourly weather condition:", err)
			continue
		}

		if day != nil && day.Date.Day() != slot.Time.Day() {
			if len(forecast) >= numdays-1 {
				break
			}
			forecast = append(forecast, *day)
			day = nil
		}
		if day == nil {
			day = new(iface.Day)
			day.Date = slot.Time
			c.parseAstro(day, days.Data)
		}

		day.Slots = append(day.Slots, slot)
	}
	return append(forecast, *day)
}

func (c *forecastConfig) parseCond(dp forecastDataPoint) (ret iface.Cond, err error) {
	codemap := map[string]iface.WeatherCode{
		"clear-day":           iface.CodeSunny,
		"clear-night":         iface.CodeSunny,
		"rain":                iface.CodeLightRain,
		"snow":                iface.CodeLightSnow,
		"sleet":               iface.CodeLightSleet,
		"wind":                iface.CodePartlyCloudy,
		"fog":                 iface.CodeFog,
		"cloudy":              iface.CodeCloudy,
		"partly-cloudy-day":   iface.CodePartlyCloudy,
		"partly-cloudy-night": iface.CodePartlyCloudy,
		"thunderstorm":        iface.CodeThunderyShowers,
	}

	if dp.Time == nil {
		return iface.Cond{}, fmt.Errorf("The forecast.io response did not provide a time for the weather condition")
	}
	ret.Time = time.Unix(*dp.Time, 0).In(c.tz)

	ret.Code = iface.CodeUnknown
	if val, ok := codemap[dp.Icon]; ok {
		ret.Code = val
	}
	ret.Desc = dp.Summary

	ret.TempC = dp.Temperature
	ret.FeelsLikeC = dp.ApparentTemperature

	if dp.PrecipProb != nil && *dp.PrecipProb >= 0 && *dp.PrecipProb <= 1 {
		p := int(*dp.PrecipProb * 100)
		ret.ChanceOfRainPercent = &p
	}

	if dp.PrecipIntensity != nil && *dp.PrecipIntensity >= 0 {
		p := *dp.PrecipIntensity / 1000
		ret.PrecipM = &p
	}

	if dp.Visibility != nil && *dp.Visibility >= 0 {
		p := *dp.Visibility * 1000
		ret.VisibleDistM = &p
	}

	if dp.WindSpeed != nil && *dp.WindSpeed >= 0 {
		ret.WindspeedKmph = dp.WindSpeed
	}

	//ret.WindGustKmph not provided by forecast.io :(

	if dp.WindBearing != nil && *dp.WindBearing >= 0 {
		p := int(*dp.WindBearing) % 360
		ret.WinddirDegree = &p
	}

	if dp.Humidity != nil && *dp.Humidity >= 0 && *dp.Humidity <= 1 {
		p := int(*dp.Humidity * 100)
		ret.Humidity = &p
	}

	return ret, nil
}

func (c *forecastConfig) fetch(url string) (*forecastResponse, error) {
	res, err := http.Get(url)
	if err != nil {
		return nil, fmt.Errorf("Unable to get (%s): %v", url, err)
	} else if res.StatusCode != 200 {
		return nil, fmt.Errorf("Unable to get (%s): http status %d", url, res.StatusCode)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err)
	}

	if c.debug {
		log.Printf("Response (%s): %s\n", url, string(body))
	}

	var resp forecastResponse
	if err = json.Unmarshal(body, &resp); err != nil {
		return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body))
	}

	if resp.Timezone == nil {
		log.Printf("No timezone set in response (%s)", url)
	} else {
		c.tz, err = time.LoadLocation(*resp.Timezone)
		if err != nil {
			log.Printf("Unknown Timezone used in response (%s)", url)
		}
	}
	return &resp, nil
}

func (c *forecastConfig) fetchToday(location string) ([]iface.Cond, error) {
	location = fmt.Sprintf("%s,%d", location, time.Now().Unix())

	resp, err := c.fetch(fmt.Sprintf(forecastWuri, c.apiKey, location, c.lang))
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch todays weather data: %v\n", err)
	}

	days := c.parseDaily(resp.Hourly, resp.Daily, 1)
	if len(days) < 1 {
		return nil, fmt.Errorf("Failed to parse today\n")
	}
	return days[0].Slots, nil
}

func (c *forecastConfig) Setup() {
	flag.StringVar(&c.apiKey, "forecast-api-key", "", "forecast backend: the api `KEY` to use")
	flag.StringVar(&c.lang, "forecast-lang", "en", "forecast backend: the `LANGUAGE` to request from forecast.io")
	flag.BoolVar(&c.debug, "forecast-debug", false, "forecast backend: print raw requests and responses")
}

func (c *forecastConfig) Fetch(location string, numdays int) iface.Data {
	var ret iface.Data
	todayChan := make(chan []iface.Cond)

	if len(c.apiKey) == 0 {
		log.Fatal("No forecast.io API key specified.\nYou have to register for one at https://developer.forecast.io/register")
	}
	if matched, err := regexp.MatchString(`^-?[0-9]*(\.[0-9]+)?,-?[0-9]*(\.[0-9]+)?$`, location); !matched || err != nil {
		log.Fatalf("Error: The forecast.io backend only supports latitude,longitude pairs as location.\nInstead of `%s` try `40.748,-73.985` for example to get a forecast for New York", location)
	}

	c.tz = time.Local

	go func() {
		slots, err := c.fetchToday(location)
		if err != nil {
			log.Fatalf("Failed to fetch todays weather data: %v\n", err)
		}
		todayChan <- slots
	}()

	resp, err := c.fetch(fmt.Sprintf(forecastWuri, c.apiKey, location, c.lang))
	if err != nil {
		log.Fatalf("Failed to fetch weather data: %v\n", err)
	}

	if resp.Latitude == nil || resp.Longitude == nil {
		log.Println("nil response for latitude,longitude")
		ret.Location = location
	} else {
		ret.GeoLoc = &iface.LatLon{Latitude: *resp.Latitude, Longitude: *resp.Longitude}
		ret.Location = fmt.Sprintf("%f,%f", *resp.Latitude, *resp.Longitude)
	}

	if ret.Current, err = c.parseCond(resp.Currently); err != nil {
		log.Fatalf("Could not parse current weather condition: %v", err)
	}
	ret.Forecast = c.parseDaily(resp.Hourly, resp.Daily, numdays)

	if numdays >= 1 {
		var tHistory, tFuture = <-todayChan, ret.Forecast[0].Slots
		var tRet []iface.Cond
		h, f := 0, 0

		// merge forecast and history from current day
		for h < len(tHistory) || f < len(tFuture) {
			if f >= len(tFuture) {
				tRet = append(tRet, tHistory[h])
				h++
			} else if h >= len(tHistory) || tHistory[h].Time.After(tFuture[f].Time) {
				tRet = append(tRet, tFuture[f])
				f++
			} else if tHistory[h].Time.Before(tFuture[f].Time) {
				tRet = append(tRet, tHistory[h])
				h++
			} else {
				tRet = append(tRet, tFuture[f])
				h++
				f++
			}
		}
		ret.Forecast[0].Slots = tRet
	}
	return ret
}

func init() {
	iface.AllBackends["forecast.io"] = &forecastConfig{}
}
<commit_msg>forecast.io backend: fix bug where at least one day of forecast was returned<commit_after>package backends

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"time"

	"github.com/schachmat/wego/iface"
)

type forecastConfig struct {
	apiKey string
	lang   string
	debug  bool
	tz     *time.Location
}

type forecastDataPoint struct {
	Time                *int64   `json:"time"`
	Summary             string   `json:"summary"`
	Icon                string   `json:"icon"`
	SunriseTime         *int64   `json:"sunriseTime"`
	SunsetTime          *int64   `json:"sunsetTime"`
	PrecipIntensity     *float32 `json:"precipIntensity"`
	PrecipProb          *float32 `json:"precipProbability"`
	Temperature         *float32 `json:"temperature"`
	ApparentTemperature *float32 `json:"apparentTemperature"`
	WindSpeed           *float32 `json:"windSpeed"`
	WindBearing         *float32 `json:"windBearing"`
	Visibility          *float32 `json:"visibility"`
	Humidity            *float32 `json:"humidity"`
}

type forecastDataBlock struct {
	Summary string              `json:"summary"`
	Icon    string              `json:"icon"`
	Data    []forecastDataPoint `json:"data"`
}

type forecastResponse struct {
	Latitude  *float32          `json:"latitude"`
	Longitude *float32          `json:"longitude"`
	Timezone  *string           `json:"timezone"`
	Currently forecastDataPoint `json:"currently"`
	Hourly    forecastDataBlock `json:"hourly"`
	Daily     forecastDataBlock `json:"daily"`
}

const (
	// see https://developer.forecast.io/docs/v2
	// see also https://github.com/mlbright/forecast
	//https://api.forecast.io/forecast/APIKEY/LATITUDE,LONGITUDE
	forecastWuri = "https://api.forecast.io/forecast/%s/%s?units=ca&lang=%s&exclude=minutely,alerts,flags&extend=hourly"
)

func (c *forecastConfig) parseAstro(cur *iface.Day, days []forecastDataPoint) {
	for _, day := range days {
		if day.Time != nil && cur.Date.Day() == time.Unix(*day.Time, 0).In(c.tz).Day() {
			if day.SunriseTime != nil {
				cur.Astronomy.Sunrise = time.Unix(*day.SunriseTime, 0).In(c.tz)
			}
			if day.SunsetTime != nil {
				cur.Astronomy.Sunset = time.Unix(*day.SunsetTime, 0).In(c.tz)
			}
			return
		}
	}
}

func (c *forecastConfig) parseDaily(hours, days forecastDataBlock, numdays int) []iface.Day {
	var forecast []iface.Day
	var day *iface.Day

	for _, hourData := range hours.Data {
		slot, err := c.parseCond(hourData)
		if err != nil {
			log.Println("Error parsing hourly weather condition:", err)
			continue
		}

		if day != nil && day.Date.Day() != slot.Time.Day() {
			if len(forecast) >= numdays-1 {
				break
			}
			forecast = append(forecast, *day)
			day = nil
		}
		if day == nil {
			day = new(iface.Day)
			day.Date = slot.Time
			c.parseAstro(day, days.Data)
		}

		day.Slots = append(day.Slots, slot)
	}
	return append(forecast, *day)
}

func (c *forecastConfig) parseCond(dp forecastDataPoint) (ret iface.Cond, err error) {
	codemap := map[string]iface.WeatherCode{
		"clear-day":           iface.CodeSunny,
		"clear-night":         iface.CodeSunny,
		"rain":                iface.CodeLightRain,
		"snow":                iface.CodeLightSnow,
		"sleet":               iface.CodeLightSleet,
		"wind":                iface.CodePartlyCloudy,
		"fog":                 iface.CodeFog,
		"cloudy":              iface.CodeCloudy,
		"partly-cloudy-day":   iface.CodePartlyCloudy,
		"partly-cloudy-night": iface.CodePartlyCloudy,
		"thunderstorm":        iface.CodeThunderyShowers,
	}

	if dp.Time == nil {
		return iface.Cond{}, fmt.Errorf("The forecast.io response did not provide a time for the weather condition")
	}
	ret.Time = time.Unix(*dp.Time, 0).In(c.tz)

	ret.Code = iface.CodeUnknown
	if val, ok := codemap[dp.Icon]; ok {
		ret.Code = val
	}
	ret.Desc = dp.Summary

	ret.TempC = dp.Temperature
	ret.FeelsLikeC = dp.ApparentTemperature

	if dp.PrecipProb != nil && *dp.PrecipProb >= 0 && *dp.PrecipProb <= 1 {
		p := int(*dp.PrecipProb * 100)
		ret.ChanceOfRainPercent = &p
	}

	if dp.PrecipIntensity != nil && *dp.PrecipIntensity >= 0 {
		p := *dp.PrecipIntensity / 1000
		ret.PrecipM = &p
	}

	if dp.Visibility != nil && *dp.Visibility >= 0 {
		p := *dp.Visibility * 1000
		ret.VisibleDistM = &p
	}

	if dp.WindSpeed != nil && *dp.WindSpeed >= 0 {
		ret.WindspeedKmph = dp.WindSpeed
	}

	//ret.WindGustKmph not provided by forecast.io :(

	if dp.WindBearing != nil && *dp.WindBearing >= 0 {
		p := int(*dp.WindBearing) % 360
		ret.WinddirDegree = &p
	}

	if dp.Humidity != nil && *dp.Humidity >= 0 && *dp.Humidity <= 1 {
		p := int(*dp.Humidity * 100)
		ret.Humidity = &p
	}

	return ret, nil
}

func (c *forecastConfig) fetch(url string) (*forecastResponse, error) {
	res, err := http.Get(url)
	if err != nil {
		return nil, fmt.Errorf("Unable to get (%s): %v", url, err)
	} else if res.StatusCode != 200 {
		return nil, fmt.Errorf("Unable to get (%s): http status %d", url, res.StatusCode)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err)
	}

	if c.debug {
		log.Printf("Response (%s): %s\n", url, string(body))
	}

	var resp forecastResponse
	if err = json.Unmarshal(body, &resp); err != nil {
		return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body))
	}

	if resp.Timezone == nil {
		log.Printf("No timezone set in response (%s)", url)
	} else {
		c.tz, err = time.LoadLocation(*resp.Timezone)
		if err != nil {
			log.Printf("Unknown Timezone used in response (%s)", url)
		}
	}
	return &resp, nil
}

func (c *forecastConfig) fetchToday(location string) ([]iface.Cond, error) {
	location = fmt.Sprintf("%s,%d", location, time.Now().Unix())

	resp, err := c.fetch(fmt.Sprintf(forecastWuri, c.apiKey, location, c.lang))
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch todays weather data: %v\n", err)
	}

	days := c.parseDaily(resp.Hourly, resp.Daily, 1)
	if len(days) < 1 {
		return nil, fmt.Errorf("Failed to parse today\n")
	}
	return days[0].Slots, nil
}

func (c *forecastConfig) Setup() {
	flag.StringVar(&c.apiKey, "forecast-api-key", "", "forecast backend: the api `KEY` to use")
	flag.StringVar(&c.lang, "forecast-lang", "en", "forecast backend: the `LANGUAGE` to request from forecast.io")
	flag.BoolVar(&c.debug, "forecast-debug", false, "forecast backend: print raw requests and responses")
}

func (c *forecastConfig) Fetch(location string, numdays int) iface.Data {
	var ret iface.Data
	todayChan := make(chan []iface.Cond)

	if len(c.apiKey) == 0 {
		log.Fatal("No forecast.io API key specified.\nYou have to register for one at https://developer.forecast.io/register")
	}
	if matched, err := regexp.MatchString(`^-?[0-9]*(\.[0-9]+)?,-?[0-9]*(\.[0-9]+)?$`, location); !matched || err != nil {
		log.Fatalf("Error: The forecast.io backend only supports latitude,longitude pairs as location.\nInstead of `%s` try `40.748,-73.985` for example to get a forecast for New York", location)
	}

	c.tz = time.Local

	go func() {
		slots, err := c.fetchToday(location)
		if err != nil {
			log.Fatalf("Failed to fetch todays weather data: %v\n", err)
		}
		todayChan <- slots
	}()

	resp, err := c.fetch(fmt.Sprintf(forecastWuri, c.apiKey, location, c.lang))
	if err != nil {
		log.Fatalf("Failed to fetch weather data: %v\n", err)
	}

	if resp.Latitude == nil || resp.Longitude == nil {
		log.Println("nil response for latitude,longitude")
		ret.Location = location
	} else {
		ret.GeoLoc = &iface.LatLon{Latitude: *resp.Latitude, Longitude: *resp.Longitude}
		ret.Location = fmt.Sprintf("%f,%f", *resp.Latitude, *resp.Longitude)
	}

	if ret.Current, err = c.parseCond(resp.Currently); err != nil {
		log.Fatalf("Could not parse current weather condition: %v", err)
	}

	if numdays >= 1 {
		ret.Forecast = c.parseDaily(resp.Hourly, resp.Daily, numdays)

		var tHistory, tFuture = <-todayChan, ret.Forecast[0].Slots
		var tRet []iface.Cond
		h, f := 0, 0

		// merge forecast and history from current day
		for h < len(tHistory) || f < len(tFuture) {
			if f >= len(tFuture) {
				tRet = append(tRet, tHistory[h])
				h++
			} else if h >= len(tHistory) || tHistory[h].Time.After(tFuture[f].Time) {
				tRet = append(tRet, tFuture[f])
				f++
			} else if tHistory[h].Time.Before(tFuture[f].Time) {
				tRet = append(tRet, tHistory[h])
				h++
			} else {
				tRet = append(tRet, tFuture[f])
				h++
				f++
			}
		}
		ret.Forecast[0].Slots = tRet
	}
	return ret
}

func init() {
	iface.AllBackends["forecast.io"] = &forecastConfig{}
}
<|endoftext|>


<commit_before>package cmd

import (
	"github.com/markbates/pop"
	"github.com/spf13/cobra"
)

var createCmd = &cobra.Command{
	Use:   "create",
	Short: "Creates databases for you",
	RunE: func(cmd *cobra.Command, args []string) error {
		var err error
		if all {
			for _, conn := range pop.Connections {
				err = pop.CreateDB(conn)
				if err != nil {
					break
				}
			}
		} else {
			err = pop.CreateDB(getConn())
		}
		return err
	},
}

func init() {
	createCmd.Flags().BoolVarP(&all, "all", "a", false, "Creates all of the databases in the database.yml")
	RootCmd.AddCommand(createCmd)
}
<commit_msg>printing instad of breaking the loop<commit_after>package cmd

import (
	"fmt"

	"github.com/markbates/pop"
	"github.com/spf13/cobra"
)

var createCmd = &cobra.Command{
	Use:   "create",
	Short: "Creates databases for you",
	RunE: func(cmd *cobra.Command, args []string) error {
		var err error
		if all {
			for _, conn := range pop.Connections {
				err = pop.CreateDB(conn)
				if err != nil {
					fmt.Println(err)
				}
			}
		} else {
			err = pop.CreateDB(getConn())
		}
		return err
	},
}

func init() {
	createCmd.Flags().BoolVarP(&all, "all", "a", false, "Creates all of the databases in the database.yml")
	RootCmd.AddCommand(createCmd)
}
<|endoftext|>


<commit_before>package middlewares

import (
	"fmt"
	"time"

	"github.com/labstack/echo/v4"
)

// CacheMode is an enum to define a cache-control mode
type CacheMode int

const (
	// NoCache is for the no-cache control mode
	NoCache CacheMode = iota + 1
	// NoStore is for the no-store control mode
	NoStore
)

// CacheOptions contains different options for the CacheControl middleware.
type CacheOptions struct {
	MaxAge         time.Duration
	Private        bool
	MustRevalidate bool
	Mode           CacheMode
}

// CacheControl returns a middleware to handle HTTP caching options.
func CacheControl(opts CacheOptions) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			cache := ""
			if opts.Private {
				cache = "private"
			}
			switch opts.Mode {
			case NoCache:
				cache = appendHeader(cache, "no-cache")
			case NoStore:
				cache = appendHeader(cache, "no-store")
			}
			if opts.MustRevalidate {
				cache = appendHeader(cache, "must-revalidate")
			}
			if maxAge := opts.MaxAge; maxAge > 0 {
				cache = appendHeader(cache, fmt.Sprintf("max-age=%d", int(maxAge.Seconds())))
			}
			if cache != "" {
				c.Response().Header().Set("Cache-Control", cache)
			}
			return next(c)
		}
	}
}

func appendHeader(h, val string) string {
	if h == "" {
		return val
	}
	return h + ", " + val
}
<commit_msg>Force public on cache-control with max-age<commit_after>package middlewares

import (
	"fmt"
	"time"

	"github.com/labstack/echo/v4"
)

// CacheMode is an enum to define a cache-control mode
type CacheMode int

const (
	// NoCache is for the no-cache control mode
	NoCache CacheMode = iota + 1
	// NoStore is for the no-store control mode
	NoStore
)

// CacheOptions contains different options for the CacheControl middleware.
type CacheOptions struct {
	MaxAge         time.Duration
	Private        bool
	MustRevalidate bool
	Mode           CacheMode
}

// CacheControl returns a middleware to handle HTTP caching options.
func CacheControl(opts CacheOptions) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			cache := "public"
			if opts.Private {
				cache = "private"
			}
			switch opts.Mode {
			case NoCache:
				cache = appendHeader(cache, "no-cache")
			case NoStore:
				cache = appendHeader(cache, "no-store")
			}
			if opts.MustRevalidate {
				cache = appendHeader(cache, "must-revalidate")
			}
			if maxAge := opts.MaxAge; maxAge > 0 {
				cache = appendHeader(cache, fmt.Sprintf("max-age=%d", int(maxAge.Seconds())))
			}
			if cache != "" {
				c.Response().Header().Set("Cache-Control", cache)
			}
			return next(c)
		}
	}
}

func appendHeader(h, val string) string {
	if h == "" {
		return val
	}
	return h + ", " + val
}
<|endoftext|>


<commit_before>package realtime

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

	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/logger"
	"github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/pkg/realtime"
	"github.com/cozy/cozy-stack/web/middlewares"
	webpermissions "github.com/cozy/cozy-stack/web/permissions"
	"github.com/cozy/echo"
	"github.com/gorilla/websocket"
)

const (
	// Time allowed to write a message to the peer
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer
	pongWait = 60 * time.Second

	// Send pings to peer with this period (must be less than pongWait)
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer
	maxMessageSize = 1024
)

var upgrader = websocket.Upgrader{
	// Don't check the origin of the connexion, the Authorization header is enough
	CheckOrigin:     func(r *http.Request) bool { return true },
	Subprotocols:    []string{"io.cozy.websocket"},
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type command struct {
	Method  string `json:"method"`
	Payload struct {
		Type string `json:"type"`
		ID   string `json:"id"`
	} `json:"payload"`
}

type wsErrorPayload struct {
	Status string      `json:"status"`
	Code   string      `json:"code"`
	Title  string      `json:"title"`
	Source interface{} `json:"source"`
}

type wsError struct {
	Event   string         `json:"event"`
	Payload wsErrorPayload `json:"payload"`
}

func unauthorized(cmd interface{}) *wsError {
	return &wsError{
		Event: "error",
		Payload: wsErrorPayload{
			Status: "401 Unauthorized",
			Code:   "unauthorized",
			Title:  "The authentication has failed",
			Source: cmd,
		},
	}
}

func forbidden(cmd *command) *wsError {
	return &wsError{
		Event: "error",
		Payload: wsErrorPayload{
			Status: "403 Forbidden",
			Code:   "forbidden",
			Title:  fmt.Sprintf("The application can't subscribe to %s", cmd.Payload.Type),
			Source: cmd,
		},
	}
}

func unknownMethod(method string, cmd interface{}) *wsError {
	return &wsError{
		Event: "error",
		Payload: wsErrorPayload{
			Status: "405 Method Not Allowed",
			Code:   "method not allowed",
			Title:  fmt.Sprintf("The %s method is not supported", method),
			Source: cmd,
		},
	}
}

func missingType(cmd *command) *wsError {
	return &wsError{
		Event: "error",
		Payload: wsErrorPayload{
			Status: "404 Page Not Found",
			Code:   "page not found",
			Title:  "The type parameter is mandatory for SUBSCRIBE",
			Source: cmd,
		},
	}
}

func readPump(i *instance.Instance, ws *websocket.Conn, ds *realtime.DynamicSubscriber, errc chan *wsError) {
	var auth map[string]string
	if err := ws.ReadJSON(&auth); err != nil {
		return
	}
	if strings.ToUpper(auth["method"]) != "AUTH" {
		errc <- unknownMethod(auth["method"], auth)
		return
	}
	if auth["payload"] == "" {
		errc <- unauthorized(auth)
		return
	}
	pdoc, err := webpermissions.ParseJWT(i, auth["payload"])
	if err != nil {
		errc <- unauthorized(auth)
		return
	}

	for {
		cmd := &command{}
		if err := ws.ReadJSON(cmd); err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
				logger.WithDomain(ds.Domain).Infof("ws error: %s", err)
			}
			break
		}

		if strings.ToUpper(cmd.Method) != "SUBSCRIBE" {
			errc <- unknownMethod(cmd.Method, cmd)
			continue
		}
		if cmd.Payload.Type == "" {
			errc <- missingType(cmd)
			continue
		}
		if !pdoc.Permissions.AllowWholeType(permissions.GET, cmd.Payload.Type) {
			errc <- forbidden(cmd)
			continue
		}

		// TODO filter by id
		// TODO what do we do with include_docs?
		if err := ds.Subscribe(cmd.Payload.Type); err != nil {
			logger.WithDomain(ds.Domain).Warnf("realtime error: %s", err)
		}
	}
}

func ws(c echo.Context) error {
	instance := middlewares.GetInstance(c)

	ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
	if err != nil {
		return err
	}
	defer ws.Close()

	ws.SetReadLimit(maxMessageSize)
	ws.SetReadDeadline(time.Now().Add(pongWait))
	ws.SetPongHandler(func(string) error {
		ws.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})

	ds := realtime.GetHub().Subscriber(instance.Domain)
	defer ds.Close()
	errc := make(chan *wsError)

	go readPump(instance, ws, ds, errc)

	ticker := time.NewTicker(pingPeriod)
	defer ticker.Stop()

	for {
		select {
		case e, ok := <-errc:
			if !ok { // Websocket has been closed by the client
				return nil
			}
			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteJSON(e); err != nil {
				return nil
			}
		case e := <-ds.Channel:
			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteJSON(e); err != nil {
				return nil
			}
		case <-ticker.C:
			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
				return nil
			}
		}
	}
}

// Routes set the routing for the realtime service
func Routes(router *echo.Group) {
	router.GET("/", ws)
}
<commit_msg>Fix gometalinter<commit_after>package realtime

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

	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/logger"
	"github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/pkg/realtime"
	"github.com/cozy/cozy-stack/web/middlewares"
	webpermissions "github.com/cozy/cozy-stack/web/permissions"
	"github.com/cozy/echo"
	"github.com/gorilla/websocket"
)

const (
	// Time allowed to write a message to the peer
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer
	pongWait = 60 * time.Second

	// Send pings to peer with this period (must be less than pongWait)
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer
	maxMessageSize = 1024
)

var upgrader = websocket.Upgrader{
	// Don't check the origin of the connexion, the Authorization header is enough
	CheckOrigin:     func(r *http.Request) bool { return true },
	Subprotocols:    []string{"io.cozy.websocket"},
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type command struct {
	Method  string `json:"method"`
	Payload struct {
		Type string `json:"type"`
		ID   string `json:"id"`
	} `json:"payload"`
}

type wsErrorPayload struct {
	Status string      `json:"status"`
	Code   string      `json:"code"`
	Title  string      `json:"title"`
	Source interface{} `json:"source"`
}

type wsError struct {
	Event   string         `json:"event"`
	Payload wsErrorPayload `json:"payload"`
}

func unauthorized(cmd interface{}) *wsError {
	return &wsError{
		Event: "error",
		Payload: wsErrorPayload{
			Status: "401 Unauthorized",
			Code:   "unauthorized",
			Title:  "The authentication has failed",
			Source: cmd,
		},
	}
}

func forbidden(cmd *command) *wsError {
	return &wsError{
		Event: "error",
		Payload: wsErrorPayload{
			Status: "403 Forbidden",
			Code:   "forbidden",
			Title:  fmt.Sprintf("The application can't subscribe to %s", cmd.Payload.Type),
			Source: cmd,
		},
	}
}

func unknownMethod(method string, cmd interface{}) *wsError {
	return &wsError{
		Event: "error",
		Payload: wsErrorPayload{
			Status: "405 Method Not Allowed",
			Code:   "method not allowed",
			Title:  fmt.Sprintf("The %s method is not supported", method),
			Source: cmd,
		},
	}
}

func missingType(cmd *command) *wsError {
	return &wsError{
		Event: "error",
		Payload: wsErrorPayload{
			Status: "404 Page Not Found",
			Code:   "page not found",
			Title:  "The type parameter is mandatory for SUBSCRIBE",
			Source: cmd,
		},
	}
}

func readPump(i *instance.Instance, ws *websocket.Conn, ds *realtime.DynamicSubscriber, errc chan *wsError) {
	var auth map[string]string
	if err := ws.ReadJSON(&auth); err != nil {
		return
	}
	if strings.ToUpper(auth["method"]) != "AUTH" {
		errc <- unknownMethod(auth["method"], auth)
		return
	}
	if auth["payload"] == "" {
		errc <- unauthorized(auth)
		return
	}
	pdoc, err := webpermissions.ParseJWT(i, auth["payload"])
	if err != nil {
		errc <- unauthorized(auth)
		return
	}

	for {
		cmd := &command{}
		if err := ws.ReadJSON(cmd); err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
				logger.WithDomain(ds.Domain).Infof("ws error: %s", err)
			}
			break
		}

		if strings.ToUpper(cmd.Method) != "SUBSCRIBE" {
			errc <- unknownMethod(cmd.Method, cmd)
			continue
		}
		if cmd.Payload.Type == "" {
			errc <- missingType(cmd)
			continue
		}
		if !pdoc.Permissions.AllowWholeType(permissions.GET, cmd.Payload.Type) {
			errc <- forbidden(cmd)
			continue
		}

		// TODO filter by id
		// TODO what do we do with include_docs?
		if err := ds.Subscribe(cmd.Payload.Type); err != nil {
			logger.WithDomain(ds.Domain).Warnf("realtime error: %s", err)
		}
	}
}

func ws(c echo.Context) error {
	instance := middlewares.GetInstance(c)

	ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
	if err != nil {
		return err
	}
	defer ws.Close()

	ws.SetReadLimit(maxMessageSize)
	if err = ws.SetReadDeadline(time.Now().Add(pongWait)); err != nil {
		return nil
	}
	ws.SetPongHandler(func(string) error {
		if err := ws.SetReadDeadline(time.Now().Add(pongWait)); err != nil {
			return err
		}
		return nil
	})

	ds := realtime.GetHub().Subscriber(instance.Domain)
	defer ds.Close()
	errc := make(chan *wsError)

	go readPump(instance, ws, ds, errc)

	ticker := time.NewTicker(pingPeriod)
	defer ticker.Stop()

	for {
		select {
		case e, ok := <-errc:
			if !ok { // Websocket has been closed by the client
				return nil
			}
			if err := ws.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
				return nil
			}
			if err := ws.WriteJSON(e); err != nil {
				return nil
			}
		case e := <-ds.Channel:
			if err := ws.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
				return err
			}
			if err := ws.WriteJSON(e); err != nil {
				return nil
			}
		case <-ticker.C:
			if err := ws.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
				return err
			}
			if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
				return nil
			}
		}
	}
}

// Routes set the routing for the realtime service
func Routes(router *echo.Group) {
	router.GET("/", ws)
}
<|endoftext|>


<commit_before>// Package settings regroups some API methods to facilitate the usage of the
// io.cozy settings documents.
package settings

import (
	"encoding/json"
	"net/http"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/cozy/cozy-stack/web/middlewares"
	webpermissions "github.com/cozy/cozy-stack/web/permissions"
	"github.com/cozy/echo"
)

type apiInstance struct {
	doc *couchdb.JSONDoc
}

func (i *apiInstance) ID() string                             { return i.doc.ID() }
func (i *apiInstance) Rev() string                            { return i.doc.Rev() }
func (i *apiInstance) DocType() string                        { return consts.Settings }
func (i *apiInstance) Clone() couchdb.Doc                     { return i }
func (i *apiInstance) SetID(id string)                        { i.doc.SetID(id) }
func (i *apiInstance) SetRev(rev string)                      { i.doc.SetRev(rev) }
func (i *apiInstance) Relationships() jsonapi.RelationshipMap { return nil }
func (i *apiInstance) Included() []jsonapi.Object             { return nil }
func (i *apiInstance) Links() *jsonapi.LinksList {
	return &jsonapi.LinksList{Self: "/settings/instance"}
}
func (i *apiInstance) MarshalJSON() ([]byte, error) {
	return json.Marshal(i.doc)
}

func getInstance(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	doc, err := inst.SettingsDocument()
	if err != nil {
		return err
	}

	doc.M["locale"] = inst.Locale
	doc.M["onboarding_finished"] = inst.OnboardingFinished
	doc.M["auto_update"] = !inst.NoAutoUpdate
	doc.M["auth_mode"] = instance.AuthModeToString(inst.AuthMode)
	doc.M["tos"] = inst.TOSSigned
	doc.M["uuid"] = inst.UUID
	doc.M["context"] = inst.ContextName

	if err = webpermissions.Allow(c, permissions.GET, doc); err != nil {
		return err
	}

	return jsonapi.Data(c, http.StatusOK, &apiInstance{doc}, nil)
}

func updateInstance(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	doc := &couchdb.JSONDoc{}
	obj, err := jsonapi.Bind(c.Request().Body, doc)
	if err != nil {
		return err
	}

	doc.Type = consts.Settings
	doc.SetID(consts.InstanceSettingsID)
	doc.SetRev(obj.Meta.Rev)

	if err = webpermissions.Allow(c, webpermissions.PUT, doc); err != nil {
		return err
	}

	if err := instance.Patch(inst, &instance.Options{SettingsObj: doc}); err != nil {
		return err
	}

	doc.M["locale"] = inst.Locale
	doc.M["onboarding_finished"] = inst.OnboardingFinished
	doc.M["auto_update"] = !inst.NoAutoUpdate
	doc.M["auth_mode"] = instance.AuthModeToString(inst.AuthMode)
	doc.M["tos"] = inst.TOSSigned
	doc.M["uuid"] = inst.UUID
	doc.M["context"] = inst.ContextName

	return jsonapi.Data(c, http.StatusOK, &apiInstance{doc}, nil)
}

func updateInstanceAuthMode(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	if err := webpermissions.AllowWholeType(c, permissions.PUT, consts.Settings); err != nil {
		return err
	}

	args := struct {
		AuthMode                string `json:"auth_mode"`
		TwoFactorActivationCode string `json:"two_factor_activation_code"`
	}{}
	if err := c.Bind(&args); err != nil {
		return err
	}

	authMode, err := instance.StringToAuthMode(args.AuthMode)
	if err != nil {
		return jsonapi.BadRequest(err)
	}
	if inst.HasAuthMode(authMode) {
		return c.NoContent(http.StatusNoContent)
	}

	switch authMode {
	case instance.Basic:
	case instance.TwoFactorMail:
		if args.TwoFactorActivationCode == "" {
			if err = inst.SendMailConfirmationCode(); err != nil {
				return err
			}
			return c.NoContent(http.StatusNoContent)
		}
		if ok := inst.ValidateMailConfirmationCode(args.TwoFactorActivationCode); !ok {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
	}

	err = instance.Patch(inst, &instance.Options{AuthMode: args.AuthMode})
	if err != nil {
		return err
	}

	return c.NoContent(http.StatusNoContent)
}
<commit_msg>Keep retro-compatibility on web/settings/instance API<commit_after>// Package settings regroups some API methods to facilitate the usage of the
// io.cozy settings documents.
package settings

import (
	"encoding/json"
	"net/http"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/cozy/cozy-stack/web/middlewares"
	webpermissions "github.com/cozy/cozy-stack/web/permissions"
	"github.com/cozy/echo"
)

type apiInstance struct {
	doc *couchdb.JSONDoc
}

func (i *apiInstance) ID() string                             { return i.doc.ID() }
func (i *apiInstance) Rev() string                            { return i.doc.Rev() }
func (i *apiInstance) DocType() string                        { return consts.Settings }
func (i *apiInstance) Clone() couchdb.Doc                     { return i }
func (i *apiInstance) SetID(id string)                        { i.doc.SetID(id) }
func (i *apiInstance) SetRev(rev string)                      { i.doc.SetRev(rev) }
func (i *apiInstance) Relationships() jsonapi.RelationshipMap { return nil }
func (i *apiInstance) Included() []jsonapi.Object             { return nil }
func (i *apiInstance) Links() *jsonapi.LinksList {
	return &jsonapi.LinksList{Self: "/settings/instance"}
}
func (i *apiInstance) MarshalJSON() ([]byte, error) {
	return json.Marshal(i.doc)
}

func getInstance(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	doc, err := inst.SettingsDocument()
	if err != nil {
		return err
	}

	doc.M["locale"] = inst.Locale
	doc.M["onboarding_finished"] = inst.OnboardingFinished
	doc.M["auto_update"] = !inst.NoAutoUpdate
	doc.M["auth_mode"] = instance.AuthModeToString(inst.AuthMode)
	doc.M["tos"] = inst.TOSSigned
	doc.M["uuid"] = inst.UUID
	doc.M["context"] = inst.ContextName

	if err = webpermissions.Allow(c, permissions.GET, doc); err != nil {
		return err
	}

	return jsonapi.Data(c, http.StatusOK, &apiInstance{doc}, nil)
}

func updateInstance(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	doc := &couchdb.JSONDoc{}
	obj, err := jsonapi.Bind(c.Request().Body, doc)
	if err != nil {
		return err
	}

	doc.Type = consts.Settings
	doc.SetID(consts.InstanceSettingsID)
	doc.SetRev(obj.Meta.Rev)

	if err = webpermissions.Allow(c, webpermissions.PUT, doc); err != nil {
		return err
	}

	pdoc, err := webpermissions.GetPermission(c)
	if err != nil || pdoc.Type != permissions.TypeCLI {
		delete(doc.M, "auth_mode")
		delete(doc.M, "tos")
		delete(doc.M, "uuid")
		delete(doc.M, "context")
	}

	if err := instance.Patch(inst, &instance.Options{SettingsObj: doc}); err != nil {
		return err
	}

	doc.M["locale"] = inst.Locale
	doc.M["onboarding_finished"] = inst.OnboardingFinished
	doc.M["auto_update"] = !inst.NoAutoUpdate
	doc.M["auth_mode"] = instance.AuthModeToString(inst.AuthMode)
	doc.M["tos"] = inst.TOSSigned
	doc.M["uuid"] = inst.UUID
	doc.M["context"] = inst.ContextName

	return jsonapi.Data(c, http.StatusOK, &apiInstance{doc}, nil)
}

func updateInstanceAuthMode(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	if err := webpermissions.AllowWholeType(c, permissions.PUT, consts.Settings); err != nil {
		return err
	}

	args := struct {
		AuthMode                string `json:"auth_mode"`
		TwoFactorActivationCode string `json:"two_factor_activation_code"`
	}{}
	if err := c.Bind(&args); err != nil {
		return err
	}

	authMode, err := instance.StringToAuthMode(args.AuthMode)
	if err != nil {
		return jsonapi.BadRequest(err)
	}
	if inst.HasAuthMode(authMode) {
		return c.NoContent(http.StatusNoContent)
	}

	switch authMode {
	case instance.Basic:
	case instance.TwoFactorMail:
		if args.TwoFactorActivationCode == "" {
			if err = inst.SendMailConfirmationCode(); err != nil {
				return err
			}
			return c.NoContent(http.StatusNoContent)
		}
		if ok := inst.ValidateMailConfirmationCode(args.TwoFactorActivationCode); !ok {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
	}

	err = instance.Patch(inst, &instance.Options{AuthMode: args.AuthMode})
	if err != nil {
		return err
	}

	return c.NoContent(http.StatusNoContent)
}
<|endoftext|>


<commit_before>// Package settings regroups some API methods to facilitate the usage of the
// io.cozy settings documents.
package settings

import (
	"encoding/json"
	"net/http"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/sessions"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/cozy-stack/web/permissions"
	"github.com/cozy/echo"
)

type apiSession struct {
	s *sessions.Session
}

func (s *apiSession) ID() string                             { return s.s.ID() }
func (s *apiSession) Rev() string                            { return s.s.Rev() }
func (s *apiSession) DocType() string                        { return consts.Sessions }
func (s *apiSession) Clone() couchdb.Doc                     { return s }
func (s *apiSession) SetID(_ string)                         {}
func (s *apiSession) SetRev(_ string)                        {}
func (s *apiSession) Relationships() jsonapi.RelationshipMap { return nil }
func (s *apiSession) Included() []jsonapi.Object             { return nil }
func (s *apiSession) Links() *jsonapi.LinksList              { return nil }
func (s *apiSession) MarshalJSON() ([]byte, error)           { return json.Marshal(s.s) }

func getSessions(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	if err := permissions.AllowWholeType(c, permissions.GET, consts.Sessions); err != nil {
		return err
	}

	sessions, err := sessions.GetAll(inst)
	if err != nil {
		return err
	}

	objs := make([]jsonapi.Object, len(sessions))
	for i, s := range sessions {
		objs[i] = &apiSession{s}
	}

	return jsonapi.DataList(c, http.StatusOK, objs, nil)
}

func warnings(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	if err := permissions.AllowWholeType(c, permissions.GET, consts.Settings); err != nil {
		return err
	}

	warnings := inst.Warnings()
	if warnings == nil {
		warnings = []*jsonapi.Error{}
	}

	return jsonapi.DataErrorList(c, warnings...)
}

// Routes sets the routing for the settings service
func Routes(router *echo.Group) {
	router.GET("/disk-usage", diskUsage)

	router.POST("/passphrase", registerPassphrase)
	router.PUT("/passphrase", updatePassphrase)

	router.GET("/instance", getInstance)
	router.PUT("/instance", updateInstance)
	router.PUT("/instance/auth_mode", updateInstanceAuthMode)

	router.GET("/sessions", getSessions)

	router.GET("/clients", listClients)
	router.DELETE("/clients/:id", revokeClient)
	router.POST("/synchronized", synchronized)

	router.GET("/onboarded", onboarded)
	router.GET("/context", context)
	router.GET("/warnings", warnings)
}
<commit_msg>Relax permissions on settings/warnings route<commit_after>// Package settings regroups some API methods to facilitate the usage of the
// io.cozy settings documents.
package settings

import (
	"encoding/json"
	"net/http"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/sessions"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/cozy-stack/web/permissions"
	"github.com/cozy/echo"
)

type apiSession struct {
	s *sessions.Session
}

func (s *apiSession) ID() string                             { return s.s.ID() }
func (s *apiSession) Rev() string                            { return s.s.Rev() }
func (s *apiSession) DocType() string                        { return consts.Sessions }
func (s *apiSession) Clone() couchdb.Doc                     { return s }
func (s *apiSession) SetID(_ string)                         {}
func (s *apiSession) SetRev(_ string)                        {}
func (s *apiSession) Relationships() jsonapi.RelationshipMap { return nil }
func (s *apiSession) Included() []jsonapi.Object             { return nil }
func (s *apiSession) Links() *jsonapi.LinksList              { return nil }
func (s *apiSession) MarshalJSON() ([]byte, error)           { return json.Marshal(s.s) }

func getSessions(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	if err := permissions.AllowWholeType(c, permissions.GET, consts.Sessions); err != nil {
		return err
	}

	sessions, err := sessions.GetAll(inst)
	if err != nil {
		return err
	}

	objs := make([]jsonapi.Object, len(sessions))
	for i, s := range sessions {
		objs[i] = &apiSession{s}
	}

	return jsonapi.DataList(c, http.StatusOK, objs, nil)
}

func warnings(c echo.Context) error {
	inst := middlewares.GetInstance(c)

	if _, err := permissions.GetPermission(c); err != nil {
		return err
	}

	warnings := inst.Warnings()
	if warnings == nil {
		warnings = []*jsonapi.Error{}
	}

	return jsonapi.DataErrorList(c, warnings...)
}

// Routes sets the routing for the settings service
func Routes(router *echo.Group) {
	router.GET("/disk-usage", diskUsage)

	router.POST("/passphrase", registerPassphrase)
	router.PUT("/passphrase", updatePassphrase)

	router.GET("/instance", getInstance)
	router.PUT("/instance", updateInstance)
	router.PUT("/instance/auth_mode", updateInstanceAuthMode)

	router.GET("/sessions", getSessions)

	router.GET("/clients", listClients)
	router.DELETE("/clients/:id", revokeClient)
	router.POST("/synchronized", synchronized)

	router.GET("/onboarded", onboarded)
	router.GET("/context", context)
	router.GET("/warnings", warnings)
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 The gocql 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 gocql

import (
	"fmt"
	"github.com/gocql/gocql/internal/streams"
	"testing"

	"github.com/hailocab/go-hostpool"
)

// Tests of the round-robin host selection policy implementation
func TestRoundRobinHostPolicy(t *testing.T) {
	policy := RoundRobinHostPolicy()

	hosts := []*HostInfo{
		{hostId: "0"},
		{hostId: "1"},
	}

	policy.SetHosts(hosts)

	// interleaved iteration should always increment the host
	iterA := policy.Pick(nil)
	if actual := iterA(); actual.Info() != hosts[0] {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actual.Info().HostID())
	}
	iterB := policy.Pick(nil)
	if actual := iterB(); actual.Info() != hosts[1] {
		t.Errorf("Expected hosts[1] but was hosts[%s]", actual.Info().HostID())
	}
	if actual := iterB(); actual.Info() != hosts[0] {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actual.Info().HostID())
	}
	if actual := iterA(); actual.Info() != hosts[1] {
		t.Errorf("Expected hosts[1] but was hosts[%s]", actual.Info().HostID())
	}

	iterC := policy.Pick(nil)
	if actual := iterC(); actual.Info() != hosts[0] {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actual.Info().HostID())
	}
	if actual := iterC(); actual.Info() != hosts[1] {
		t.Errorf("Expected hosts[1] but was hosts[%s]", actual.Info().HostID())
	}
}

// Tests of the token-aware host selection policy implementation with a
// round-robin host selection policy fallback.
func TestTokenAwareHostPolicy(t *testing.T) {
	policy := TokenAwareHostPolicy(RoundRobinHostPolicy())

	query := &Query{}

	iter := policy.Pick(nil)
	if iter == nil {
		t.Fatal("host iterator was nil")
	}
	actual := iter()
	if actual != nil {
		t.Fatalf("expected nil from iterator, but was %v", actual)
	}

	// set the hosts
	hosts := []*HostInfo{
		{peer: "0", tokens: []string{"00"}},
		{peer: "1", tokens: []string{"25"}},
		{peer: "2", tokens: []string{"50"}},
		{peer: "3", tokens: []string{"75"}},
	}
	policy.SetHosts(hosts)

	// the token ring is not setup without the partitioner, but the fallback
	// should work
	if actual := policy.Pick(nil)(); actual.Info().Peer() != "0" {
		t.Errorf("Expected peer 0 but was %s", actual.Info().Peer())
	}

	query.RoutingKey([]byte("30"))
	if actual := policy.Pick(query)(); actual.Info().Peer() != "1" {
		t.Errorf("Expected peer 1 but was %s", actual.Info().Peer())
	}

	policy.SetPartitioner("OrderedPartitioner")

	// now the token ring is configured
	query.RoutingKey([]byte("20"))
	iter = policy.Pick(query)
	if actual := iter(); actual.Info().Peer() != "1" {
		t.Errorf("Expected peer 1 but was %s", actual.Info().Peer())
	}
	// rest are round robin
	if actual := iter(); actual.Info().Peer() != "2" {
		t.Errorf("Expected peer 2 but was %s", actual.Info().Peer())
	}
	if actual := iter(); actual.Info().Peer() != "3" {
		t.Errorf("Expected peer 3 but was %s", actual.Info().Peer())
	}
	if actual := iter(); actual.Info().Peer() != "0" {
		t.Errorf("Expected peer 0 but was %s", actual.Info().Peer())
	}
}

// Tests of the host pool host selection policy implementation
func TestHostPoolHostPolicy(t *testing.T) {
	policy := HostPoolHostPolicy(hostpool.New(nil))

	hosts := []*HostInfo{
		{hostId: "0", peer: "0"},
		{hostId: "1", peer: "1"},
	}

	policy.SetHosts(hosts)

	// the first host selected is actually at [1], but this is ok for RR
	// interleaved iteration should always increment the host
	iter := policy.Pick(nil)
	actualA := iter()
	if actualA.Info().HostID() != "0" {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actualA.Info().HostID())
	}
	actualA.Mark(nil)

	actualB := iter()
	if actualB.Info().HostID() != "1" {
		t.Errorf("Expected hosts[1] but was hosts[%s]", actualB.Info().HostID())
	}
	actualB.Mark(fmt.Errorf("error"))

	actualC := iter()
	if actualC.Info().HostID() != "0" {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actualC.Info().HostID())
	}
	actualC.Mark(nil)

	actualD := iter()
	if actualD.Info().HostID() != "0" {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actualD.Info().HostID())
	}
	actualD.Mark(nil)
}

// Tests of the round-robin connection selection policy implementation
func TestRoundRobinConnPolicy(t *testing.T) {
	policy := RoundRobinConnPolicy()()

	conn0 := &Conn{streams: streams.New(1)}
	conn1 := &Conn{streams: streams.New(1)}
	conn := []*Conn{
		conn0,
		conn1,
	}

	policy.SetConns(conn)

	if actual := policy.Pick(nil); actual != conn0 {
		t.Error("Expected conn1")
	}
	if actual := policy.Pick(nil); actual != conn1 {
		t.Error("Expected conn0")
	}
	if actual := policy.Pick(nil); actual != conn0 {
		t.Error("Expected conn1")
	}
}

func TestRoundRobinNilHostInfo(t *testing.T) {
	policy := RoundRobinHostPolicy()

	host := &HostInfo{hostId: "host-1"}
	policy.SetHosts([]*HostInfo{host})

	iter := policy.Pick(nil)
	next := iter()
	if next == nil {
		t.Fatal("got nil host")
	} else if v := next.Info(); v == nil {
		t.Fatal("got nil HostInfo")
	} else if v.HostID() != host.HostID() {
		t.Fatalf("expected host %v got %v", host, *v)
	}

	next = iter()
	if next != nil {
		t.Errorf("expected to get nil host got %+v", next)
		if next.Info() == nil {
			t.Fatalf("HostInfo is nil")
		}
	}
}
<commit_msg>add test for COW list adding<commit_after>// Copyright (c) 2015 The gocql 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 gocql

import (
	"fmt"
	"github.com/gocql/gocql/internal/streams"
	"testing"

	"github.com/hailocab/go-hostpool"
)

// Tests of the round-robin host selection policy implementation
func TestRoundRobinHostPolicy(t *testing.T) {
	policy := RoundRobinHostPolicy()

	hosts := []*HostInfo{
		{hostId: "0"},
		{hostId: "1"},
	}

	policy.SetHosts(hosts)

	// interleaved iteration should always increment the host
	iterA := policy.Pick(nil)
	if actual := iterA(); actual.Info() != hosts[0] {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actual.Info().HostID())
	}
	iterB := policy.Pick(nil)
	if actual := iterB(); actual.Info() != hosts[1] {
		t.Errorf("Expected hosts[1] but was hosts[%s]", actual.Info().HostID())
	}
	if actual := iterB(); actual.Info() != hosts[0] {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actual.Info().HostID())
	}
	if actual := iterA(); actual.Info() != hosts[1] {
		t.Errorf("Expected hosts[1] but was hosts[%s]", actual.Info().HostID())
	}

	iterC := policy.Pick(nil)
	if actual := iterC(); actual.Info() != hosts[0] {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actual.Info().HostID())
	}
	if actual := iterC(); actual.Info() != hosts[1] {
		t.Errorf("Expected hosts[1] but was hosts[%s]", actual.Info().HostID())
	}
}

// Tests of the token-aware host selection policy implementation with a
// round-robin host selection policy fallback.
func TestTokenAwareHostPolicy(t *testing.T) {
	policy := TokenAwareHostPolicy(RoundRobinHostPolicy())

	query := &Query{}

	iter := policy.Pick(nil)
	if iter == nil {
		t.Fatal("host iterator was nil")
	}
	actual := iter()
	if actual != nil {
		t.Fatalf("expected nil from iterator, but was %v", actual)
	}

	// set the hosts
	hosts := []*HostInfo{
		{peer: "0", tokens: []string{"00"}},
		{peer: "1", tokens: []string{"25"}},
		{peer: "2", tokens: []string{"50"}},
		{peer: "3", tokens: []string{"75"}},
	}
	policy.SetHosts(hosts)

	// the token ring is not setup without the partitioner, but the fallback
	// should work
	if actual := policy.Pick(nil)(); actual.Info().Peer() != "0" {
		t.Errorf("Expected peer 0 but was %s", actual.Info().Peer())
	}

	query.RoutingKey([]byte("30"))
	if actual := policy.Pick(query)(); actual.Info().Peer() != "1" {
		t.Errorf("Expected peer 1 but was %s", actual.Info().Peer())
	}

	policy.SetPartitioner("OrderedPartitioner")

	// now the token ring is configured
	query.RoutingKey([]byte("20"))
	iter = policy.Pick(query)
	if actual := iter(); actual.Info().Peer() != "1" {
		t.Errorf("Expected peer 1 but was %s", actual.Info().Peer())
	}
	// rest are round robin
	if actual := iter(); actual.Info().Peer() != "2" {
		t.Errorf("Expected peer 2 but was %s", actual.Info().Peer())
	}
	if actual := iter(); actual.Info().Peer() != "3" {
		t.Errorf("Expected peer 3 but was %s", actual.Info().Peer())
	}
	if actual := iter(); actual.Info().Peer() != "0" {
		t.Errorf("Expected peer 0 but was %s", actual.Info().Peer())
	}
}

// Tests of the host pool host selection policy implementation
func TestHostPoolHostPolicy(t *testing.T) {
	policy := HostPoolHostPolicy(hostpool.New(nil))

	hosts := []*HostInfo{
		{hostId: "0", peer: "0"},
		{hostId: "1", peer: "1"},
	}

	policy.SetHosts(hosts)

	// the first host selected is actually at [1], but this is ok for RR
	// interleaved iteration should always increment the host
	iter := policy.Pick(nil)
	actualA := iter()
	if actualA.Info().HostID() != "0" {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actualA.Info().HostID())
	}
	actualA.Mark(nil)

	actualB := iter()
	if actualB.Info().HostID() != "1" {
		t.Errorf("Expected hosts[1] but was hosts[%s]", actualB.Info().HostID())
	}
	actualB.Mark(fmt.Errorf("error"))

	actualC := iter()
	if actualC.Info().HostID() != "0" {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actualC.Info().HostID())
	}
	actualC.Mark(nil)

	actualD := iter()
	if actualD.Info().HostID() != "0" {
		t.Errorf("Expected hosts[0] but was hosts[%s]", actualD.Info().HostID())
	}
	actualD.Mark(nil)
}

// Tests of the round-robin connection selection policy implementation
func TestRoundRobinConnPolicy(t *testing.T) {
	policy := RoundRobinConnPolicy()()

	conn0 := &Conn{streams: streams.New(1)}
	conn1 := &Conn{streams: streams.New(1)}
	conn := []*Conn{
		conn0,
		conn1,
	}

	policy.SetConns(conn)

	if actual := policy.Pick(nil); actual != conn0 {
		t.Error("Expected conn1")
	}
	if actual := policy.Pick(nil); actual != conn1 {
		t.Error("Expected conn0")
	}
	if actual := policy.Pick(nil); actual != conn0 {
		t.Error("Expected conn1")
	}
}

func TestRoundRobinNilHostInfo(t *testing.T) {
	policy := RoundRobinHostPolicy()

	host := &HostInfo{hostId: "host-1"}
	policy.SetHosts([]*HostInfo{host})

	iter := policy.Pick(nil)
	next := iter()
	if next == nil {
		t.Fatal("got nil host")
	} else if v := next.Info(); v == nil {
		t.Fatal("got nil HostInfo")
	} else if v.HostID() != host.HostID() {
		t.Fatalf("expected host %v got %v", host, *v)
	}

	next = iter()
	if next != nil {
		t.Errorf("expected to get nil host got %+v", next)
		if next.Info() == nil {
			t.Fatalf("HostInfo is nil")
		}
	}
}

func TestCOWList_Add(t *testing.T) {
	var cow cowHostList

	toAdd := [...]string{"peer1", "peer2", "peer3"}

	for _, addr := range toAdd {
		if !cow.add(&HostInfo{peer: addr}) {
			t.Fatal("did not add peer which was not in the set")
		}
	}

	hosts := cow.get()
	if len(hosts) != len(toAdd) {
		t.Fatalf("expected to have %d hosts got %d", len(toAdd), len(hosts))
	}

	set := make(map[string]bool)
	for _, host := range hosts {
		set[host.Peer()] = true
	}

	for _, addr := range toAdd {
		if !set[addr] {
			t.Errorf("addr was not in the host list: %q", addr)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright © 2016 Joseph Wright <rjosephwright@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package lib

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/kms"
	"github.com/aws/aws-sdk-go/service/s3"
)

const CiphertextLength = 204

type KmsClient struct {
	*kms.KMS
}

type S3Client struct {
	*s3.S3
}

type BossVaultClient struct {
	Kms *KmsClient
	S3  *S3Client
}

func NewBossVaultClient() *BossVaultClient {
	s := session.New()
	return &BossVaultClient{
		Kms: &KmsClient{kms.New(s)},
		S3:  &S3Client{s3.New(s)},
	}
}

func (kc *KmsClient) DataKey(keyId string) (out *kms.GenerateDataKeyOutput, err error) {
	keySpec := "AES_256"
	out, err = kc.GenerateDataKey(&kms.GenerateDataKeyInput{
		KeyId:   &keyId,
		KeySpec: &keySpec,
	})
	if err != nil {
		return
	}
	return
}

func (kc *KmsClient) KeyIdForAlias(alias string) (string, error) {
	var keyId string
	var err error

	aliases := []*kms.AliasListEntry{}
	err = kc.ListAliasesPages(
		&kms.ListAliasesInput{},
		func(out *kms.ListAliasesOutput, lastPage bool) bool {
			aliases = append(aliases, out.Aliases...)
			return lastPage
		},
	)
	if err != nil {
		return keyId, err
	}

	fullAlias := fmt.Sprintf("alias/%s", alias)
	for _, a := range aliases {
		if *a.AliasName == fullAlias {
			keyId = *a.TargetKeyId
			break
		}
	}
	if keyId == "" {
		err = fmt.Errorf("No master key found with alias %s", alias)
	}

	return keyId, err
}

func RandomBytes(n int) ([]byte, error) {
	buf := make([]byte, n)
	if _, err := io.ReadFull(rand.Reader, buf); err != nil {
		return nil, err
	}
	return buf, nil
}

func ContentBytes(content string) ([]byte, error) {
	var buf []byte
	var err error
	parts := strings.Split(content, "@")
	if len(parts) > 1 && parts[0] == "" && len(parts[1]) > 0 {
		path := parts[1]
		if file, err := os.Open(path); err != nil {
			return nil, err
		} else {
			defer file.Close()
			if buf, err = ioutil.ReadAll(file); err != nil {
				return nil, err
			}
		}
	} else {
		buf = []byte(content)
	}
	return buf, err
}

func NsFrom(artifact string) (ns string, err error) {
	parts := strings.Split(artifact, "/")
	if len(parts) == 1 {
		err = fmt.Errorf("Invalid artifact name")
	} else {
		ns = parts[0]
	}
	return ns, err
}

func (c *BossVaultClient) EncryptAndStore(bucket, artifact, content string) error {
	namespace, err := NsFrom(artifact)
	if err != nil {
		return err
	}

	plaintext, err := ContentBytes(content)
	if err != nil {
		return err
	}

	keyId, err := c.Kms.KeyIdForAlias(namespace)
	if err != nil {
		return err
	}

	dk, err := c.Kms.DataKey(keyId)
	if err != nil {
		return err
	}

	encrypted, err := Encrypt(plaintext, dk.Plaintext)
	if err != nil {
		return err
	}

	sse := "aws:kms"
	key := fmt.Sprintf("%s.enc", artifact)
	payload := append(dk.CiphertextBlob, encrypted...)
	body := bytes.NewReader(payload)
	_, err = c.S3.PutObject(
		&s3.PutObjectInput{
			Bucket:               &bucket,
			Key:                  &key,
			Body:                 body,
			ServerSideEncryption: &sse,
		},
	)
	if err != nil {
		return err
	}

	return nil
}

func (c *BossVaultClient) RetrieveAndDecrypt(bucket, artifact string) ([]byte, error) {
	obj, err := c.S3.GetObject(
		&s3.GetObjectInput{
			Bucket: &bucket,
			Key:    &artifact,
		},
	)
	if err != nil {
		return nil, err
	}

	content, err := ioutil.ReadAll(obj.Body)
	if err != nil {
		return nil, err
	}

	encryptedKey := content[:CiphertextLength]
	payload := content[CiphertextLength:]

	dataKey, err := c.Kms.Decrypt(
		&kms.DecryptInput{
			CiphertextBlob: encryptedKey,
		},
	)
	if err != nil {
		return nil, err
	}

	decrypted, err := Decrypt(payload, dataKey.Plaintext)
	if err != nil {
		return nil, err
	}

	return decrypted, nil
}

func Encrypt(plaintext, key []byte) ([]byte, error) {
	var block cipher.Block
	var err error
	var iv []byte

	if block, err = aes.NewCipher(key); err != nil {
		return nil, err
	}

	if iv, err = RandomBytes(aes.BlockSize); err != nil {
		return nil, err
	}

	ciphertext := make([]byte, len(plaintext))
	cfb := cipher.NewCFBEncrypter(block, iv)
	cfb.XORKeyStream(ciphertext, plaintext)

	return append(iv, ciphertext...), nil
}

func Decrypt(ciphertext, key []byte) ([]byte, error) {
	iv := ciphertext[:aes.BlockSize]
	payload := ciphertext[aes.BlockSize:]
	decrypted := make([]byte, len(payload))

	if block, err := aes.NewCipher(key); err != nil {
		return nil, err
	} else {
		cfb := cipher.NewCFBDecrypter(block, iv)
		cfb.XORKeyStream(decrypted, payload)
	}

	return decrypted, nil
}
<commit_msg>Rearrange and make internal functions private<commit_after>// Copyright © 2016 Joseph Wright <rjosephwright@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package lib

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/kms"
	"github.com/aws/aws-sdk-go/service/s3"
)

const CiphertextLength = 204

type BossVaultClient struct {
	kms *kmsClient
	s3  *s3Client
}

func NewBossVaultClient() *BossVaultClient {
	s := session.New()
	return &BossVaultClient{
		kms: &kmsClient{kms.New(s)},
		s3:  &s3Client{s3.New(s)},
	}
}

func (c *BossVaultClient) EncryptAndStore(bucket, artifact, content string) error {
	namespace, err := nsFrom(artifact)
	if err != nil {
		return err
	}

	plaintext, err := contentBytes(content)
	if err != nil {
		return err
	}

	keyId, err := c.kms.keyIdForAlias(namespace)
	if err != nil {
		return err
	}

	dk, err := c.kms.dataKey(keyId)
	if err != nil {
		return err
	}

	encrypted, err := encrypt(plaintext, dk.Plaintext)
	if err != nil {
		return err
	}

	return c.s3.store(artifact, bucket, encrypted, dk.CiphertextBlob)
}

func (c *BossVaultClient) RetrieveAndDecrypt(bucket, artifact string) ([]byte, error) {
	obj, err := c.s3.GetObject(
		&s3.GetObjectInput{
			Bucket: &bucket,
			Key:    &artifact,
		},
	)
	if err != nil {
		return nil, err
	}

	content, err := ioutil.ReadAll(obj.Body)
	if err != nil {
		return nil, err
	}

	encryptedKey := content[:CiphertextLength]
	payload := content[CiphertextLength:]

	dk, err := c.kms.Decrypt(
		&kms.DecryptInput{
			CiphertextBlob: encryptedKey,
		},
	)
	if err != nil {
		return nil, err
	}

	decrypted, err := decrypt(payload, dk.Plaintext)
	if err != nil {
		return nil, err
	}

	return decrypted, nil
}

type kmsClient struct {
	*kms.KMS
}

type s3Client struct {
	*s3.S3
}

func (kc *kmsClient) dataKey(keyId string) (out *kms.GenerateDataKeyOutput, err error) {
	keySpec := "AES_256"
	out, err = kc.GenerateDataKey(&kms.GenerateDataKeyInput{
		KeyId:   &keyId,
		KeySpec: &keySpec,
	})
	if err != nil {
		return
	}
	return
}

func (kc *kmsClient) keyIdForAlias(alias string) (string, error) {
	var keyId string
	var err error

	aliases := []*kms.AliasListEntry{}
	err = kc.ListAliasesPages(
		&kms.ListAliasesInput{},
		func(out *kms.ListAliasesOutput, lastPage bool) bool {
			aliases = append(aliases, out.Aliases...)
			return lastPage
		},
	)
	if err != nil {
		return keyId, err
	}

	fullAlias := fmt.Sprintf("alias/%s", alias)
	for _, a := range aliases {
		if *a.AliasName == fullAlias {
			keyId = *a.TargetKeyId
			break
		}
	}
	if keyId == "" {
		err = fmt.Errorf("No master key found with alias %s", alias)
	}

	return keyId, err
}

func (sc *s3Client) store(artifact, bucket string, encrypted, encryptedKey []byte) error {
	sse := "aws:kms"
	key := fmt.Sprintf("%s.enc", artifact)
	payload := append(encryptedKey, encrypted...)
	body := bytes.NewReader(payload)
	_, err := sc.PutObject(
		&s3.PutObjectInput{
			Bucket:               &bucket,
			Key:                  &key,
			Body:                 body,
			ServerSideEncryption: &sse,
		},
	)
	return err
}

func randomBytes(n int) ([]byte, error) {
	buf := make([]byte, n)
	if _, err := io.ReadFull(rand.Reader, buf); err != nil {
		return nil, err
	}
	return buf, nil
}

func contentBytes(content string) ([]byte, error) {
	var buf []byte
	var err error
	parts := strings.Split(content, "@")
	if len(parts) > 1 && parts[0] == "" && len(parts[1]) > 0 {
		path := parts[1]
		if file, err := os.Open(path); err != nil {
			return nil, err
		} else {
			defer file.Close()
			if buf, err = ioutil.ReadAll(file); err != nil {
				return nil, err
			}
		}
	} else {
		buf = []byte(content)
	}
	return buf, err
}

func nsFrom(artifact string) (ns string, err error) {
	parts := strings.Split(artifact, "/")
	if len(parts) == 1 {
		err = fmt.Errorf("Invalid artifact name")
	} else {
		ns = parts[0]
	}
	return ns, err
}

func encrypt(plaintext, key []byte) ([]byte, error) {
	var block cipher.Block
	var err error
	var iv []byte

	if block, err = aes.NewCipher(key); err != nil {
		return nil, err
	}

	if iv, err = randomBytes(aes.BlockSize); err != nil {
		return nil, err
	}

	ciphertext := make([]byte, len(plaintext))
	cfb := cipher.NewCFBEncrypter(block, iv)
	cfb.XORKeyStream(ciphertext, plaintext)

	return append(iv, ciphertext...), nil
}

func decrypt(ciphertext, key []byte) ([]byte, error) {
	iv := ciphertext[:aes.BlockSize]
	payload := ciphertext[aes.BlockSize:]
	decrypted := make([]byte, len(payload))

	if block, err := aes.NewCipher(key); err != nil {
		return nil, err
	} else {
		cfb := cipher.NewCFBDecrypter(block, iv)
		cfb.XORKeyStream(decrypted, payload)
	}

	return decrypted, nil
}
<|endoftext|>