mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-02-01 02:22:26 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b9e733a7d | ||
|
|
df78e00df3 | ||
|
|
b5ed4e6a13 | ||
|
|
500b444bad | ||
|
|
d3e2859238 | ||
|
|
5686591420 | ||
|
|
6903196c18 | ||
|
|
af4cd1b515 | ||
|
|
2acfafd5a5 | ||
|
|
a5067cdbb3 | ||
|
|
5aa08756e0 | ||
|
|
0e66b0b165 | ||
|
|
87660611d2 | ||
|
|
9968c0d234 | ||
|
|
193fcb87bb | ||
|
|
a08e4ec043 | ||
|
|
e38a7548cc | ||
|
|
b5dea42bbe | ||
|
|
d06fe15a68 | ||
|
|
e6301f0e74 | ||
|
|
aca33e45fb |
@@ -12,7 +12,7 @@ See [SCOPE.md](./SCOPE.md) for some guidelines about what we consider in and out
|
||||
|
||||
## Community Meetings
|
||||
|
||||
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
|
||||
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM ET. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
|
||||
|
||||
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ Theme|Description|Timeline|
|
||||
|--|--|--|
|
||||
|LDAP Support|Extends upstream IDP protocols|May 2021|
|
||||
|Improved Documentation|Reorganizing and improving Pinniped docs; new how-to guides and tutorials|May 2021|
|
||||
|Multiple IDPs|Support for multiple upstream IDPs to be configured simultaneously|Jun 2021|
|
||||
|Device Code Flow|Add support for OAuth 2.0 Device Authorization Grant in the Pinniped CLI and Supervisor|Jun 2021|
|
||||
|AD Support|Extends upstream IDP protocols|Jun 2021|
|
||||
|Wider Concierge cluster support|Support for more cluster types in the Concierge|Jul 2021|
|
||||
|Improving Security Posture|Offer the best security posture for Kubernetes cluster authentication|Exploring/Ongoing|
|
||||
|Improve our CI/CD systems|Upgrade tests; make Kind more efficient and reliable for CI ; Windows tests; performance tests; scale tests; soak tests|Exploring/Ongoing|
|
||||
|
||||
@@ -260,7 +260,7 @@ spec:
|
||||
externalEndpoint: #@ data.values.impersonation_proxy_spec.external_endpoint
|
||||
#@ end
|
||||
service:
|
||||
mode: #@ data.values.impersonation_proxy_spec.service.mode
|
||||
type: #@ data.values.impersonation_proxy_spec.service.type
|
||||
#@ if data.values.impersonation_proxy_spec.service.load_balancer_ip:
|
||||
loadBalancerIP: #@ data.values.impersonation_proxy_spec.service.load_balancer_ip
|
||||
#@ end
|
||||
|
||||
@@ -87,7 +87,7 @@ impersonation_proxy_spec:
|
||||
#! impersonation proxy.
|
||||
#! None does not provision either and assumes that you have set the external_endpoint
|
||||
#! and set up your own ingress to connect to the impersonation proxy.
|
||||
mode: LoadBalancer
|
||||
type: LoadBalancer
|
||||
#! The annotations that should be set on the ClusterIP or LoadBalancer Service.
|
||||
annotations:
|
||||
{service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "4000"}
|
||||
|
||||
@@ -477,7 +477,8 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.C
|
||||
c.infoLog.Info("deleting load balancer for impersonation proxy",
|
||||
"service", klog.KRef(c.namespace, c.generatedLoadBalancerServiceName),
|
||||
)
|
||||
return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{})
|
||||
err = c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{})
|
||||
return utilerrors.FilterOut(err, k8serrors.IsNotFound)
|
||||
}
|
||||
|
||||
func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx context.Context, config *v1alpha1.ImpersonationProxySpec) error {
|
||||
@@ -516,7 +517,8 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx conte
|
||||
c.infoLog.Info("deleting cluster ip for impersonation proxy",
|
||||
"service", klog.KRef(c.namespace, c.generatedClusterIPServiceName),
|
||||
)
|
||||
return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedClusterIPServiceName, metav1.DeleteOptions{})
|
||||
err = c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedClusterIPServiceName, metav1.DeleteOptions{})
|
||||
return utilerrors.FilterOut(err, k8serrors.IsNotFound)
|
||||
}
|
||||
|
||||
func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context, service *v1.Service) error {
|
||||
|
||||
@@ -43,6 +43,8 @@ Pinniped supports the following IDPs.
|
||||
identity provider (e.g., [Dex](https://github.com/dexidp/dex),
|
||||
[Okta](https://www.okta.com/)).
|
||||
|
||||
1. Any [LDAP](https://ldap.com) identity provider.
|
||||
|
||||
The
|
||||
[`idp.supervisor.pinniped.dev`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-idp-supervisor-pinniped-dev-v1alpha1)
|
||||
API group contains the Kubernetes custom resources that configure the Pinniped
|
||||
|
||||
@@ -44,10 +44,10 @@ Click Open to allow the command to proceed.
|
||||
|
||||
## Install a specific version via script
|
||||
|
||||
For example, to install v0.8.0 on Linux/amd64:
|
||||
For example, to install v0.9.0 on Linux/amd64:
|
||||
|
||||
```sh
|
||||
curl -Lso pinniped https://get.pinniped.dev/v0.8.0/pinniped-cli-linux-amd64 \
|
||||
curl -Lso pinniped https://get.pinniped.dev/v0.9.0/pinniped-cli-linux-amd64 \
|
||||
&& chmod +x pinniped \
|
||||
&& sudo mv pinniped /usr/local/bin/pinniped
|
||||
```
|
||||
|
||||
@@ -26,9 +26,9 @@ Warning: the default configuration may create a public LoadBalancer Service on y
|
||||
|
||||
1. Install the Concierge into the `pinniped-concierge` namespace with default options:
|
||||
|
||||
- `kubectl apply -f https://get.pinniped.dev/v0.8.0/install-pinniped-concierge.yaml`
|
||||
- `kubectl apply -f https://get.pinniped.dev/v0.9.0/install-pinniped-concierge.yaml`
|
||||
|
||||
*Replace v0.8.0 with your preferred version number.*
|
||||
*Replace v0.9.0 with your preferred version number.*
|
||||
|
||||
## With custom options
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ You should have a supported Kubernetes cluster with working HTTPS ingress capabi
|
||||
|
||||
1. Install the Supervisor into the `pinniped-supervisor` namespace with default options:
|
||||
|
||||
- `kubectl apply -f https://get.pinniped.dev/v0.8.0/install-pinniped-supervisor.yaml`
|
||||
- `kubectl apply -f https://get.pinniped.dev/v0.9.0/install-pinniped-supervisor.yaml`
|
||||
|
||||
*Replace v0.8.0 with your preferred version number.*
|
||||
*Replace v0.9.0 with your preferred version number.*
|
||||
|
||||
## With custom options
|
||||
|
||||
|
||||
@@ -21,33 +21,35 @@ pinniped get kubeconfig [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
||||
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
|
||||
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
|
||||
--concierge-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge
|
||||
--concierge-credential-issuer string Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)
|
||||
--concierge-endpoint string API base for the Concierge endpoint
|
||||
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
|
||||
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
|
||||
--credential-cache string Path to cluster-specific credentials cache
|
||||
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
|
||||
-h, --help help for kubeconfig
|
||||
--kubeconfig string Path to kubeconfig file
|
||||
--kubeconfig-context string Kubeconfig context name (default: current active context)
|
||||
--no-concierge Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly
|
||||
--oidc-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
|
||||
--oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
|
||||
--oidc-issuer string OpenID Connect issuer URL (default: autodiscover)
|
||||
--oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only)
|
||||
--oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange
|
||||
--oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience])
|
||||
--oidc-session-cache string Path to OpenID Connect session cache file
|
||||
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
|
||||
-o, --output string Output file path (default: stdout)
|
||||
--skip-validation Skip final validation of the kubeconfig (default: false)
|
||||
--static-token string Instead of doing an OIDC-based login, specify a static token
|
||||
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
|
||||
--timeout duration Timeout for autodiscovery and validation (default 10m0s)
|
||||
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
||||
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
|
||||
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
|
||||
--concierge-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge
|
||||
--concierge-credential-issuer string Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)
|
||||
--concierge-endpoint string API base for the Concierge endpoint
|
||||
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
|
||||
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
|
||||
--credential-cache string Path to cluster-specific credentials cache
|
||||
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
|
||||
-h, --help help for kubeconfig
|
||||
--kubeconfig string Path to kubeconfig file
|
||||
--kubeconfig-context string Kubeconfig context name (default: current active context)
|
||||
--no-concierge Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly
|
||||
--oidc-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
|
||||
--oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
|
||||
--oidc-issuer string OpenID Connect issuer URL (default: autodiscover)
|
||||
--oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only)
|
||||
--oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange
|
||||
--oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience])
|
||||
--oidc-session-cache string Path to OpenID Connect session cache file
|
||||
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
|
||||
-o, --output string Output file path (default: stdout)
|
||||
--skip-validation Skip final validation of the kubeconfig (default: false)
|
||||
--static-token string Instead of doing an OIDC-based login, specify a static token
|
||||
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
|
||||
--timeout duration Timeout for autodiscovery and validation (default 10m0s)
|
||||
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
|
||||
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap')
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
@@ -79,7 +79,7 @@ as the authenticator.
|
||||
see [deploy/local-user-authenticator/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/local-user-authenticator/README.md)
|
||||
for instructions on how to deploy using `ytt`.
|
||||
|
||||
If you prefer to install a specific version, replace `latest` in the URL with the version number such as `v0.8.0`.
|
||||
If you prefer to install a specific version, replace `latest` in the URL with the version number such as `v0.9.0`.
|
||||
|
||||
1. Create a test user named `pinny-the-seal` in the local-user-authenticator namespace.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Pinniped v0.9.0: Bring Your LDAP Identities to Your Kubernetes Clusters"
|
||||
slug: bringing-ldap-identities-to-clusters
|
||||
date: 2021-05-31
|
||||
date: 2021-06-02
|
||||
author: Ryan Richard
|
||||
image: https://cdn.pixabay.com/photo/2018/08/05/15/06/seal-3585727_1280.jpg
|
||||
excerpt: "With the release of v0.9.0, Pinniped now supports using LDAP identities to log in to Kubernetes clusters."
|
||||
@@ -12,7 +12,7 @@ tags: ['Ryan Richard', 'release']
|
||||
*Photo from [matos11 on Pixabay](https://pixabay.com/photos/seal-animal-water-hairy-3585727/)*
|
||||
|
||||
Pinniped is a “batteries included” authentication system for Kubernetes clusters.
|
||||
With the release of v0.9.0, Pinniped now supports using LDAP identities to log in to Kubernetes clusters.
|
||||
With the [release of v0.9.0](https://github.com/vmware-tanzu/pinniped/releases/tag/v0.9.0), Pinniped now supports using LDAP identities to log in to Kubernetes clusters.
|
||||
|
||||
This post describes how v0.9.0 fits into Pinniped’s quest to bring a smooth, unified login experience to all Kubernetes clusters.
|
||||
|
||||
@@ -136,7 +136,9 @@ Additionally, SAML seems to be waning in popularity in favor of OIDC, which prov
|
||||
What do you think? Do you still use SAML in your enterprise?
|
||||
Do you need SAML for authentication into your Kubernetes clusters? Let us know!
|
||||
|
||||
### We'd love to hear from you!
|
||||
## Community contributors
|
||||
|
||||
The Pinniped community continues to grow, and is a vital part of the project's success. This release includes important feedback and contributions from community user [@jeuniii](https://github.com/jeuniii). Thank you for helping improve Pinniped!
|
||||
|
||||
We thrive on community feedback. Did you try our new LDAP features?
|
||||
What else do you need from identity systems for your Kubernetes clusters?
|
||||
@@ -1,5 +1,8 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@@ -50,8 +53,7 @@ spec:
|
||||
serviceAccountName: cert-issuer
|
||||
initContainers:
|
||||
- name: generate-certs
|
||||
image: cfssl/cfssl:1.5.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
image: #@ data.values.cfssl_image
|
||||
command: ["/bin/bash"]
|
||||
args:
|
||||
- -c
|
||||
@@ -108,7 +110,7 @@ spec:
|
||||
mountPath: /var/certs
|
||||
containers:
|
||||
- name: save-certs
|
||||
image: bitnami/kubectl
|
||||
image: #@ data.values.kubectl_image
|
||||
command: ["/bin/bash"]
|
||||
args:
|
||||
- -c
|
||||
|
||||
@@ -68,8 +68,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: dex
|
||||
image: ghcr.io/dexidp/dex:v2.27.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
image: #@ data.values.dex_image
|
||||
command:
|
||||
- /usr/local/bin/dex
|
||||
- serve
|
||||
|
||||
@@ -209,8 +209,7 @@ spec:
|
||||
#! Use our own fork of docker.io/bitnami/openldap for now, because we added the
|
||||
#! LDAP_SERVER_CONFIG_BEFORE_CUSTOM_LDIF_DIR and LDAP_SERVER_CONFIG_AFTER_CUSTOM_LDIF_DIR options.
|
||||
#! See https://github.com/pinniped-ci-bot/bitnami-docker-openldap/tree/pinniped
|
||||
image: projects.registry.vmware.com/pinniped/test-ldap:latest
|
||||
imagePullPolicy: Always
|
||||
image: #@ data.values.ldap_image
|
||||
ports:
|
||||
- name: ldap
|
||||
containerPort: 1389
|
||||
|
||||
@@ -25,8 +25,7 @@ spec:
|
||||
emptyDir: {}
|
||||
containers:
|
||||
- name: proxy
|
||||
image: projects.registry.vmware.com/pinniped/test-forward-proxy
|
||||
imagePullPolicy: Always
|
||||
image: #@ data.values.proxy_image
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3128
|
||||
@@ -48,7 +47,7 @@ spec:
|
||||
periodSeconds: 5
|
||||
failureThreshold: 2
|
||||
- name: accesslogs
|
||||
image: debian:10.8-slim
|
||||
image: #@ data.values.proxy_image
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
|
||||
@@ -25,3 +25,10 @@ pinny_bcrypt_passwd_hash:
|
||||
|
||||
#! The plaintext password of the LDAP test account user.
|
||||
pinny_ldap_password:
|
||||
|
||||
#! Images for each of the deployed test components.
|
||||
dex_image: ghcr.io/dexidp/dex:v2.27.0
|
||||
ldap_image: projects.registry.vmware.com/pinniped/test-ldap:latest
|
||||
proxy_image: projects.registry.vmware.com/pinniped/test-forward-proxy:latest
|
||||
cfssl_image: cfssl/cfssl:1.5.0
|
||||
kubectl_image: bitnami/kubectl:latest
|
||||
|
||||
@@ -87,16 +87,6 @@ func TestCredentialIssuer(t *testing.T) {
|
||||
},
|
||||
actualStatusKubeConfigInfo,
|
||||
)
|
||||
|
||||
// Only validate LastUpdateTime when cluster signing key is available. The last update time
|
||||
// will be set every time our controllers resync, but only when there exists controller
|
||||
// manager pods (all other pods will be filtered out), hence why this assertion is in this
|
||||
// if branch.
|
||||
//
|
||||
// This behavior is up for debate. We should eventually discuss the contract for this
|
||||
// LastUpdateTime field and ensure that the implementation is the same for when the cluster
|
||||
// signing key is available and not available.
|
||||
require.WithinDuration(t, time.Now(), actualStatusStrategy.LastUpdateTime.Local(), 10*time.Minute)
|
||||
} else {
|
||||
require.Equal(t, configv1alpha1.ErrorStrategyStatus, actualStatusStrategy.Status)
|
||||
require.Equal(t, configv1alpha1.CouldNotFetchKeyStrategyReason, actualStatusStrategy.Reason)
|
||||
|
||||
@@ -1267,9 +1267,9 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
})
|
||||
|
||||
// wait until the credential issuer is updated with the new url
|
||||
require.Eventually(t, func() bool {
|
||||
library.RequireEventuallyWithoutError(t, func() (bool, error) {
|
||||
newImpersonationProxyURL, _ := performImpersonatorDiscovery(ctx, t, env, adminConciergeClient)
|
||||
return newImpersonationProxyURL == "https://"+clusterIPServiceURL
|
||||
return newImpersonationProxyURL == "https://"+clusterIPServiceURL, nil
|
||||
}, 30*time.Second, 500*time.Millisecond)
|
||||
newImpersonationProxyURL, newImpersonationProxyCACertPEM := performImpersonatorDiscovery(ctx, t, env, adminConciergeClient)
|
||||
|
||||
|
||||
@@ -540,11 +540,29 @@ func requestAuthorizationUsingLDAPIdentityProvider(t *testing.T, downstreamAutho
|
||||
authRequest.Header.Set("Pinniped-Username", upstreamUsername)
|
||||
authRequest.Header.Set("Pinniped-Password", upstreamPassword)
|
||||
|
||||
authResponse, err := httpClient.Do(authRequest)
|
||||
require.NoError(t, err)
|
||||
responseBody, err := ioutil.ReadAll(authResponse.Body)
|
||||
defer authResponse.Body.Close()
|
||||
require.NoError(t, err)
|
||||
// At this point in the test, we've already waited for the LDAPIdentityProvider to be loaded and marked healthy by
|
||||
// at least one Supervisor pod, but we can't be sure that _all_ of them have loaded the provider, so we may need
|
||||
// to retry this request multiple times until we get the expected 302 status response.
|
||||
var authResponse *http.Response
|
||||
var responseBody []byte
|
||||
library.RequireEventuallyWithoutError(t, func() (bool, error) {
|
||||
authResponse, err = httpClient.Do(authRequest)
|
||||
if err != nil {
|
||||
t.Logf("got authorization response with error %v", err)
|
||||
return false, nil
|
||||
}
|
||||
defer func() { _ = authResponse.Body.Close() }()
|
||||
responseBody, err = ioutil.ReadAll(authResponse.Body)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
t.Logf("got authorization response with code %d (%d byte body)", authResponse.StatusCode, len(responseBody))
|
||||
if authResponse.StatusCode != http.StatusFound {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}, 30*time.Second, 200*time.Millisecond)
|
||||
|
||||
expectSecurityHeaders(t, authResponse, true)
|
||||
|
||||
// A successful authorize request results in a redirect to our localhost callback listener with an authcode param.
|
||||
|
||||
Reference in New Issue
Block a user