go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/appengine/gaeauth/server/settings.go (about)

     1  // Copyright 2016 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package server
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"html"
    21  	"html/template"
    22  	"net/url"
    23  	"strings"
    24  
    25  	"go.chromium.org/luci/appengine/gaeauth/server/internal/authdbimpl"
    26  	"go.chromium.org/luci/gae/service/info"
    27  	"go.chromium.org/luci/server/portal"
    28  )
    29  
    30  type settingsPage struct {
    31  	portal.BasePage
    32  }
    33  
    34  func (settingsPage) Title(ctx context.Context) (string, error) {
    35  	return "Authorization settings", nil
    36  }
    37  
    38  func (settingsPage) Overview(ctx context.Context) (template.HTML, error) {
    39  	serviceAcc, err := info.ServiceAccount(ctx)
    40  	if err != nil {
    41  		return "", err
    42  	}
    43  	return template.HTML(fmt.Sprintf(`<p>LUCI apps should be configured with an
    44  URL to some existing <a href="https://github.com/luci/luci-py/blob/master/appengine/auth_service/README.md">LUCI Auth Service</a>.</p>
    45  
    46  <p>This service distributes various authorization related configuration (like
    47  the list of user groups, IP allowlists, OAuth client IDs, etc), which is
    48  required to handle incoming requests. There's usually one instance of this
    49  service per LUCI deployment.</p>
    50  
    51  <p>To connect this app to LUCI Auth Service:</p>
    52  <ul>
    53    <li>
    54      Figure out what instance of LUCI Auth Service to use. Use a development
    55      instance of LUCI Auth Service (*-dev) when running code locally or deploying
    56      to a staging instance.
    57    </li>
    58    <li>
    59      Make sure Google Cloud Pub/Sub API is enabled in the Cloud Console project
    60      of your app. LUCI Auth Service uses Pub/Sub to propagate changes.
    61    </li>
    62    <li>
    63      Add the service account belonging to your app (<b>%s</b>) to
    64      <b>auth-trusted-services</b> group on LUCI Auth Service. This authorizes
    65      your app to receive updates from LUCI Auth Service.
    66    </li>
    67    <li>
    68      Enter the hostname of LUCI Auth Service in the field below and hit
    69      "Save Settings". It will verify everything is properly configured (or return
    70      an error message with some clues if not).
    71    </li>
    72  </ul>`, html.EscapeString(serviceAcc))), nil
    73  }
    74  
    75  func (settingsPage) Fields(ctx context.Context) ([]portal.Field, error) {
    76  	return []portal.Field{
    77  		{
    78  			ID:    "AuthServiceURL",
    79  			Title: "Auth service URL",
    80  			Type:  portal.FieldText,
    81  			Validator: func(authServiceURL string) (err error) {
    82  				if authServiceURL != "" {
    83  					_, err = normalizeAuthServiceURL(authServiceURL)
    84  				}
    85  				return err
    86  			},
    87  		},
    88  		{
    89  			ID:    "LatestRev",
    90  			Title: "Latest fetched revision",
    91  			Type:  portal.FieldStatic,
    92  		},
    93  	}, nil
    94  }
    95  
    96  func (settingsPage) ReadSettings(ctx context.Context) (map[string]string, error) {
    97  	switch info, err := authdbimpl.GetLatestSnapshotInfo(ctx); {
    98  	case err != nil:
    99  		return nil, err
   100  	case info == nil:
   101  		return map[string]string{
   102  			"AuthServiceURL": "",
   103  			"LatestRev":      "unknown (not configured)",
   104  		}, nil
   105  	default:
   106  		return map[string]string{
   107  			"AuthServiceURL": info.AuthServiceURL,
   108  			"LatestRev":      fmt.Sprintf("%d", info.Rev),
   109  		}, nil
   110  	}
   111  }
   112  
   113  func (settingsPage) WriteSettings(ctx context.Context, values map[string]string) error {
   114  	authServiceURL := values["AuthServiceURL"]
   115  	if authServiceURL != "" {
   116  		var err error
   117  		authServiceURL, err = normalizeAuthServiceURL(authServiceURL)
   118  		if err != nil {
   119  			return err
   120  		}
   121  	}
   122  	baseURL := "https://" + info.DefaultVersionHostname(ctx)
   123  	return authdbimpl.ConfigureAuthService(ctx, baseURL, authServiceURL)
   124  }
   125  
   126  func normalizeAuthServiceURL(authServiceURL string) (string, error) {
   127  	if !strings.Contains(authServiceURL, "://") {
   128  		authServiceURL = "https://" + authServiceURL
   129  	}
   130  	parsed, err := url.Parse(authServiceURL)
   131  	if err != nil {
   132  		return "", fmt.Errorf("bad URL %q - %s", authServiceURL, err)
   133  	}
   134  	if !parsed.IsAbs() || parsed.Path != "" {
   135  		return "", fmt.Errorf("bad URL %q - must be host root URL", authServiceURL)
   136  	}
   137  	return parsed.String(), nil
   138  }
   139  
   140  func init() {
   141  	portal.RegisterPage("auth_service", settingsPage{})
   142  }