コード例 #1
0
ファイル: sierraapi.go プロジェクト: cu-library/tyro
func SendRequestToAPI(apiURL, token string, w http.ResponseWriter, r *http.Request) (*http.Response, error) {

	l.Log(fmt.Sprintf("Sending request %v to Sierra API with token %v", apiURL, token), l.TraceMessage)

	req, err := http.NewRequest("GET", apiURL, nil)
	if err != nil {
		http.Error(w, "Request failed.", http.StatusInternalServerError)
		return new(http.Response), err
	}

	req.Close = true

	err = SetAuthorizationHeaders(req, r, token)
	if err != nil {
		l.Log("The remote address in an incoming request is not set properly.", l.WarnMessage)
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		http.Error(w, "Error querying Sierra API.", http.StatusInternalServerError)
		return resp, err
	}
	l.Log(fmt.Sprintf("Sending response %#v back to caller", resp), l.TraceMessage)
	return resp, nil

}
コード例 #2
0
ファイル: main.go プロジェクト: cu-library/tyro
func newBibsHandler(w http.ResponseWriter, r *http.Request) {

	setACAOHeader(w, r, *headerACAO)

	entries := make(map[int]sierraapi.BibRecordOut)

	entries, err := getNewItems(entries, time.Now(), w, r)
	if err != nil {
		return
	}

	var response sierraapi.BibRecordsOut

	for _, entry := range entries {
		response = append(response, entry)
	}

	sort.Sort(sort.Reverse(response))

	finalJSON, err := json.Marshal(response)
	if err != nil {
		http.Error(w, "JSON Encoding Error", http.StatusInternalServerError)
		l.Log(fmt.Sprintf("Internal Server Error at /new handler, JSON Encoding Error: %v", err), l.WarnMessage)
		return
	}

	l.Log(fmt.Sprintf("Sending response at /new handler: %v", response), l.TraceMessage)

	w.Header().Set("Content-Type", "application/json;charset=UTF-8 ")
	w.Write(finalJSON)
}
コード例 #3
0
ファイル: main.go プロジェクト: cu-library/tyro
func homeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	if r.URL.Path != "/" {
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprint(w, "<html><head></head><body><pre>404 - Not Found</pre></body></html>")
		l.Log("404 Handler visited.", l.TraceMessage)
		return
	}
	l.Log("Home Handler visited.", l.TraceMessage)
	fmt.Fprint(w, "<html><head></head><body><h1>Welcome to Tyro! The Sierra API helper.</h1></body></html>")
}
コード例 #4
0
ファイル: tokenstore.go プロジェクト: cu-library/tyro
func (t *TokenStore) refresh(tokenURL, clientKey, clientSecret string) (int, error) {

	type AuthTokenResponse struct {
		AccessToken string `json:"access_token"`
		TokenType   string `json:"token_type"`
		ExpiresIn   int    `json:"expires_in"`
	}

	bodyValues := url.Values{}
	bodyValues.Set("grant_type", "client_credentials")
	getTokenRequest, err := http.NewRequest("POST", tokenURL, bytes.NewBufferString(bodyValues.Encode()))
	if err != nil {
		t.set("")
		l.Log(err, l.WarnMessage)
		return 0, err
	}
	getTokenRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	getTokenRequest.SetBasicAuth(clientKey, clientSecret)
	getTokenRequest.Close = true
	client := new(http.Client)
	resp, err := client.Do(getTokenRequest)
	if err != nil {
		t.set("")
		l.Log(err, l.WarnMessage)
		return 0, err
	}
	if resp.StatusCode != 200 {
		t.set("")
		l.Log(err, l.WarnMessage)
		return 0, fmt.Errorf("Unable to authenticate to token generator, %v", resp.StatusCode)
	}

	var responseJSON AuthTokenResponse

	err = json.NewDecoder(resp.Body).Decode(&responseJSON)
	defer resp.Body.Close()

	if err != nil {
		t.set("")
		l.Log(err, l.WarnMessage)
		return 0, err
	}

	if responseJSON.ExpiresIn < MinimumTokenTTL {
		t.set("")
		return 0, errors.New("Token has a expire_in that is too small.")
	} else {
		l.Log("Received Token", l.TraceMessage)
		t.set(responseJSON.AccessToken)
		return responseJSON.ExpiresIn, nil
	}
}
コード例 #5
0
ファイル: main.go プロジェクト: cu-library/tyro
func getTokenOrError(w http.ResponseWriter, r *http.Request) (string, error) {

	token, err := tokenStore.Get()
	if err != nil {
		http.Error(w, "Token Error.", http.StatusInternalServerError)
		return token, err
	}
	if token == tokenstore.UninitialedTokenValue {
		l.Log("Waiting for token to initialize...", l.TraceMessage)
		select {
		case <-tokenStore.Initialized:
			tokenStore.Initialized <- struct{}{}
			token, err = tokenStore.Get()
			if err != nil {
				http.Error(w, "Token Error.", http.StatusInternalServerError)
				return token, err
			}
		case <-time.After(time.Second * 30):
			http.Error(w, "Token Error.", http.StatusInternalServerError)
			return token, errors.New("Unable to get token from TokenStore")
		}
	}

	return token, err
}
コード例 #6
0
ファイル: main.go プロジェクト: cu-library/tyro
func getNumberOfEntries(date time.Time, w http.ResponseWriter, r *http.Request) (int, error) {

	type totalResponse struct {
		Total int `json:"total"`
	}

	token, err := getTokenOrError(w, r)
	if err != nil {
		l.Log(err, l.ErrorMessage)
		return 0, err
	}

	parsedAPIURL, err := parseURLandJoinToPath(*apiURL, sierraapi.BibRequestEndpoint)
	if err != nil {
		http.Error(w, "Server Error.", http.StatusInternalServerError)
		l.Log("Internal Server Error at /new handler, unable to parse url.", l.DebugMessage)
		return 0, err
	}

	q := parsedAPIURL.Query()
	q.Set("limit", "1")
	q.Set("offset", "0")
	q.Set("deleted", "false")
	q.Set("suppressed", "false")
	q.Set("createdDate", fmt.Sprintf("[%v,%v]", date.AddDate(0, 0, -1).Format(time.RFC3339), date.Format(time.RFC3339)))
	q.Set("fields", "default")
	parsedAPIURL.RawQuery = q.Encode()

	resp, err := sierraapi.SendRequestToAPI(parsedAPIURL.String(), token, w, r)
	if err != nil {
		l.Log(fmt.Sprintf("Internal Server Error at /new, %v", err), l.ErrorMessage)
		return 0, err
	}
	if resp.StatusCode == http.StatusUnauthorized {
		http.Error(w, "Token is out of date, or is refreshing. Try request again.", http.StatusInternalServerError)
		tokenStore.Refresh <- struct{}{}
		l.Log("Token is out of date.", l.ErrorMessage)
		return 0, errors.New("Unauthorized")
	}

	var response totalResponse

	err = json.NewDecoder(resp.Body).Decode(&response)

	return response.Total, nil

}
コード例 #7
0
ファイル: tokenstore.go プロジェクト: cu-library/tyro
func (t *TokenStore) Get() (string, error) {
	t.lock.RLock()
	defer t.lock.RUnlock()
	if t.value == "" {
		return "", errors.New("Token generation error.")
	}
	l.Log("Sending token.", l.TraceMessage)
	return t.value, nil
}
コード例 #8
0
ファイル: tokenstore.go プロジェクト: cu-library/tyro
//This function runs forever, waiting for a timeout
//or a message on the Refresh channel. It will exit if the Refresh
//channel is closed.
func (t *TokenStore) Refresher(tokenURL, clientKey, clientSecret string) {

	runRefreshSetUpNext := func() <-chan time.Time {
		refreshIn, err := t.refresh(tokenURL, clientKey, clientSecret)
		if err != nil {
			l.Log(err, l.ErrorMessage)
			refreshIn = DefaultRefreshTime + TokenRefreshBuffer
		}
		futureTime := refreshIn - TokenRefreshBuffer
		lm := fmt.Sprintf("%v seconds in the future, a refresh will happen.", futureTime)
		l.Log(lm, l.TraceMessage)
		return time.After(time.Duration(futureTime) * time.Second)
	}

	refreshOrTimeout := func(timeout <-chan time.Time) (<-chan time.Time, error) {
		select {
		case <-timeout:
			l.Log("The old token timed out.", l.TraceMessage)
			return runRefreshSetUpNext(), nil
		case _, ok := <-t.Refresh:
			if ok {
				l.Log("A new token has been requested", l.TraceMessage)
				return runRefreshSetUpNext(), nil
			} else {
				return make(<-chan time.Time), errors.New("Refresh channel is closed.")
			}
		}
	}

	go func() {
		toc := runRefreshSetUpNext()
		err := errors.New("")
		for {
			toc, err = refreshOrTimeout(toc)
			if err != nil {
				return
			}
		}
	}()

}
コード例 #9
0
ファイル: main.go プロジェクト: cu-library/tyro
func rawRewriter(r *http.Request) {

	token, err := tokenStore.Get()
	if err != nil {
		l.Log("Error at /raw/ handler, token not yet generated.", l.DebugMessage)
	}

	parsedAPIURL, err := parseURLandJoinToPath(*apiURL, r.URL.Path[len("/raw/"):])
	if err != nil {
		log.Fatalf("FATAL: %v", err)
	}

	parsedAPIURL.RawQuery = r.URL.RawQuery

	r.URL = parsedAPIURL

	err = sierraapi.SetAuthorizationHeaders(r, r, token)
	if err != nil {
		l.Log("The remote address in an incoming request is not set properly", l.DebugMessage)
	}

	l.Log(fmt.Sprintf("Sending proxied request: %v", r), l.TraceMessage)

}
コード例 #10
0
ファイル: main.go プロジェクト: cu-library/tyro
func main() {

	flag.Parse()

	l.Set(l.ParseLogLevel(*logLevel))

	overrideUnsetFlagsFromEnvironmentVariables()

	l.SetupLumberjack(
		*logFileLocation,
		*logMaxSize,
		*logMaxBackups,
		*logMaxAge)

	l.Log("Starting Tyro", l.InfoMessage)
	l.Log("Serving on address: "+*address, l.InfoMessage)
	l.Log("Using Client Key: "+*clientKey, l.InfoMessage)
	l.Log("Using Client Secret: "+*clientSecret, l.InfoMessage)
	l.Log("Connecting to API URL: "+*apiURL, l.InfoMessage)
	l.Log("Using ACAO header: "+*headerACAO, l.InfoMessage)
	l.Log(fmt.Sprintf("Allowing access to raw Sierra API: %v", *raw), l.InfoMessage)

	if *clientKey == "" {
		log.Fatal("FATAL: A client key is required to authenticate against the Sierra API.")
	} else if *clientSecret == "" {
		log.Fatal("FATAL: A client secret is required to authenticate against the Sierra API.")
	}

	if *headerACAO == "*" {
		l.Log("Using \"*\" for \"Access-Control-Allow-Origin\" header. API will be public!", l.WarnMessage)
	}

	if *certFile != "" {
		l.Log("Going to try to serve through HTTPS", l.InfoMessage)
		l.Log("Using Certificate File: "+*certFile, l.InfoMessage)
		l.Log("Using Private Key File: "+*keyFile, l.InfoMessage)
	}

	parsedURL, err := parseURLandJoinToPath(*apiURL, sierraapi.TokenRequestEndpoint)
	if err != nil {
		log.Fatal("FATAL: Unable to parse API URL.")
	}

	tokenStore.Refresher(parsedURL.String(), *clientKey, *clientSecret)
	defer close(tokenStore.Refresh)

	http.HandleFunc("/", homeHandler)
	http.HandleFunc("/status/", statusHandler)
	http.HandleFunc("/status/item/", statusItemHandler)
	http.HandleFunc("/status/bib/", statusBibHandler)
	http.HandleFunc("/new", newBibsHandler)
	if *raw {
		l.Log("Allowing access to raw Sierra API.", l.WarnMessage)
		rawProxy := httputil.NewSingleHostReverseProxy(&url.URL{})
		rawProxy.Director = rawRewriter
		http.Handle("/raw/", rawProxy)
	}

	if *certFile == "" {
		log.Fatalf("FATAL: %v", http.ListenAndServe(*address, nil))
	} else {
		//Remove SSL 3.0 compatibility for POODLE exploit mitigation
		config := &tls.Config{MinVersion: tls.VersionTLS10}
		server := &http.Server{Addr: *address, Handler: nil, TLSConfig: config}
		log.Fatalf("FATAL: %v", server.ListenAndServeTLS(*certFile, *keyFile))
	}

}
コード例 #11
0
ファイル: main.go プロジェクト: cu-library/tyro
func getNewItems(alreadyProcessed map[int]sierraapi.BibRecordOut, date time.Time, w http.ResponseWriter, r *http.Request) (map[int]sierraapi.BibRecordOut, error) {

	token, err := getTokenOrError(w, r)
	if err != nil {
		l.Log(err, l.ErrorMessage)
		return nil, err
	}

	parsedAPIURL, err := parseURLandJoinToPath(*apiURL, sierraapi.BibRequestEndpoint)
	if err != nil {
		http.Error(w, "Server Error.", http.StatusInternalServerError)
		l.Log("Internal Server Error at /new handler, unable to parse url.", l.DebugMessage)
		return nil, err
	}

	total, err := getNumberOfEntries(date, w, r)
	if err != nil {
		return nil, err
	}
	offset := 0
	needOneMoreDay := false

	if total >= *newLimit {
		offset = total - *newLimit
	} else {
		needOneMoreDay = true
	}

	q := parsedAPIURL.Query()
	q.Set("offset", strconv.Itoa(offset))
	q.Set("deleted", "false")
	q.Set("createdDate", fmt.Sprintf("[%v,%v]", date.AddDate(0, 0, -1).Format(time.RFC3339), date.Format(time.RFC3339)))
	q.Set("fields", "marc,default")
	q.Set("suppressed", "false")
	parsedAPIURL.RawQuery = q.Encode()

	resp, err := sierraapi.SendRequestToAPI(parsedAPIURL.String(), token, w, r)
	if err != nil {
		l.Log(fmt.Sprintf("Internal Server Error at /new, %v", err), l.ErrorMessage)
		return nil, err
	}
	if resp.StatusCode == http.StatusUnauthorized {
		http.Error(w, "Token is out of date, or is refreshing. Try request again.", http.StatusInternalServerError)
		tokenStore.Refresh <- struct{}{}
		l.Log("Token is out of date.", l.ErrorMessage)
		return nil, err
	}

	var response sierraapi.BibRecordsIn

	err = json.NewDecoder(resp.Body).Decode(&response)

	defer resp.Body.Close()
	if err != nil {
		http.Error(w, "JSON Decoding Error", http.StatusInternalServerError)
		l.Log(fmt.Sprintf("Internal Server Error at /new handler, JSON Decoding Error: %v", err), l.WarnMessage)
		return nil, err
	}

	entries := response.Convert()

	for _, entry := range *entries {
		alreadyProcessed[entry.BibID] = entry
	}

	if needOneMoreDay {
		return getNewItems(alreadyProcessed, date.Add(time.Duration(1435)*time.Minute*-1), w, r)
	} else {
		return alreadyProcessed, nil
	}

}
コード例 #12
0
ファイル: main.go プロジェクト: cu-library/tyro
func statusBibHandler(w http.ResponseWriter, r *http.Request) {

	setACAOHeader(w, r, *headerACAO)

	token, err := getTokenOrError(w, r)
	if err != nil {
		l.Log(err, l.ErrorMessage)
		return
	}

	bibID := strings.Split(r.URL.Path[len("/status/bib/"):], "/")[0]

	if bibID == "" {
		http.Error(w, "Error, you need to provide a BibID. /status/bib/[BidID]", http.StatusBadRequest)
		l.Log("Bad Request at /status/bib/ handler, no BidID provided.", l.TraceMessage)
		return
	}

	parsedAPIURL, err := parseURLandJoinToPath(*apiURL, sierraapi.ItemRequestEndpoint)
	if err != nil {
		http.Error(w, "Server Error.", http.StatusInternalServerError)
		l.Log("Internal Server Error at /status/bib/ handler, unable to parse url.", l.DebugMessage)
		return
	}

	q := parsedAPIURL.Query()
	q.Set("bibIds", bibID)
	q.Set("deleted", "false")
	q.Set("suppressed", "false")
	parsedAPIURL.RawQuery = q.Encode()

	resp, err := sierraapi.SendRequestToAPI(parsedAPIURL.String(), token, w, r)
	if err != nil {
		l.Log(fmt.Sprintf("Internal Server Error at /status/bib/, %v", err), l.ErrorMessage)
		return
	}
	if resp.StatusCode == http.StatusUnauthorized {
		http.Error(w, "Token is out of date, or is refreshing. Try request again.", http.StatusInternalServerError)
		tokenStore.Refresh <- struct{}{}
		l.Log("Token is out of date.", l.ErrorMessage)
		return
	}
	if resp.StatusCode == http.StatusNotFound {
		http.Error(w, "No item records for that BibID.", http.StatusNotFound)
		l.Log(fmt.Sprintf("No items records match BibID %v", bibID), l.TraceMessage)
		return
	}

	var responseJSON sierraapi.ItemRecordsIn

	err = json.NewDecoder(resp.Body).Decode(&responseJSON)
	defer resp.Body.Close()
	if err != nil {
		http.Error(w, "JSON Decoding Error", http.StatusInternalServerError)
		l.Log(fmt.Sprintf("Internal Server Error at /status/bib/ handler, JSON Decoding Error: %v", err), l.WarnMessage)
		return
	}

	finalJSON, err := json.Marshal(responseJSON.Convert())
	if err != nil {
		http.Error(w, "JSON Encoding Error", http.StatusInternalServerError)
		l.Log(fmt.Sprintf("Internal Server Error at /status/bib/ handler, JSON Encoding Error: %v", err), l.WarnMessage)
		return
	}

	l.Log(fmt.Sprintf("Sending response at /status/bib/ handler: %v", responseJSON.Convert()), l.TraceMessage)

	w.Header().Set("Content-Type", "application/json;charset=UTF-8")
	w.Write(finalJSON)

}
コード例 #13
0
ファイル: main.go プロジェクト: cu-library/tyro
func statusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	w.WriteHeader(http.StatusBadRequest)
	l.Log("Bare Status Handler visited.", l.TraceMessage)
	fmt.Fprint(w, "<html><head></head><body><pre>Available endpoints: /status/bib/[bibID] and /status/item/[itemID]</pre></body></html>")
}