Compare commits
7 Commits
release/2.
...
fix/EE-107
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e8bfe003b | ||
|
|
5b68c4365e | ||
|
|
9cd64664cc | ||
|
|
e831fa4a03 | ||
|
|
2a3c807978 | ||
|
|
a8265a44d0 | ||
|
|
71ad21598b |
@@ -5,7 +5,6 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
@@ -134,14 +133,6 @@ func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User)
|
||||
return handler.persistAndWriteToken(w, composeTokenData(user))
|
||||
}
|
||||
|
||||
func (handler *Handler) writeTokenForOAuth(w http.ResponseWriter, user *portainer.User, expiryTime *time.Time) *httperror.HandlerError {
|
||||
token, err := handler.JWTService.GenerateTokenForOAuth(composeTokenData(user), expiryTime)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to generate JWT token", Err: err}
|
||||
}
|
||||
return response.JSON(w, &authenticateResponse{JWT: token})
|
||||
}
|
||||
|
||||
func (handler *Handler) persistAndWriteToken(w http.ResponseWriter, tokenData *portainer.TokenData) *httperror.HandlerError {
|
||||
token, err := handler.JWTService.GenerateToken(tokenData)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
@@ -26,21 +25,21 @@ func (payload *oauthPayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuthSettings) (string, *time.Time, error) {
|
||||
func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuthSettings) (string, error) {
|
||||
if code == "" {
|
||||
return "", nil, errors.New("Invalid OAuth authorization code")
|
||||
return "", errors.New("Invalid OAuth authorization code")
|
||||
}
|
||||
|
||||
if settings == nil {
|
||||
return "", nil, errors.New("Invalid OAuth configuration")
|
||||
return "", errors.New("Invalid OAuth configuration")
|
||||
}
|
||||
|
||||
username, expiryTime, err := handler.OAuthService.Authenticate(code, settings)
|
||||
username, err := handler.OAuthService.Authenticate(code, settings)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return username, expiryTime, nil
|
||||
return username, nil
|
||||
}
|
||||
|
||||
// @id ValidateOAuth
|
||||
@@ -70,7 +69,7 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
|
||||
return &httperror.HandlerError{StatusCode: http.StatusForbidden, Message: "OAuth authentication is not enabled", Err: errors.New("OAuth authentication is not enabled")}
|
||||
}
|
||||
|
||||
username, expiryTime, err := handler.authenticateOAuth(payload.Code, &settings.OAuthSettings)
|
||||
username, err := handler.authenticateOAuth(payload.Code, &settings.OAuthSettings)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - OAuth authentication error: %s", err)
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to authenticate through OAuth", Err: httperrors.ErrUnauthorized}
|
||||
@@ -111,5 +110,5 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
|
||||
|
||||
}
|
||||
|
||||
return handler.writeTokenForOAuth(w, user, expiryTime)
|
||||
return handler.writeToken(w, user)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// @title PortainerCE API
|
||||
// @version 2.1.1
|
||||
// @version 2.6.1
|
||||
// @description.markdown api-description.md
|
||||
// @termsOfService
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
@@ -27,18 +26,18 @@ func NewService() *Service {
|
||||
// Authenticate takes an access code and exchanges it for an access token from portainer OAuthSettings token endpoint.
|
||||
// On success, it will then return the username and token expiry time associated to authenticated user by fetching this information
|
||||
// from the resource server and matching it with the user identifier setting.
|
||||
func (*Service) Authenticate(code string, configuration *portainer.OAuthSettings) (string, *time.Time, error) {
|
||||
func (*Service) Authenticate(code string, configuration *portainer.OAuthSettings) (string, error) {
|
||||
token, err := getOAuthToken(code, configuration)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - Failed retrieving access token: %v", err)
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
username, err := getUsername(token.AccessToken, configuration)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - Failed retrieving oauth user name: %v", err)
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
return username, &token.Expiry, nil
|
||||
return username, nil
|
||||
}
|
||||
|
||||
func getOAuthToken(code string, configuration *portainer.OAuthSettings) (*oauth2.Token, error) {
|
||||
|
||||
@@ -1189,7 +1189,7 @@ type (
|
||||
|
||||
// OAuthService represents a service used to authenticate users using OAuth
|
||||
OAuthService interface {
|
||||
Authenticate(code string, configuration *OAuthSettings) (string, *time.Time, error)
|
||||
Authenticate(code string, configuration *OAuthSettings) (string, error)
|
||||
}
|
||||
|
||||
// RegistryService represents a service for managing registry data
|
||||
@@ -1341,7 +1341,7 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "2.6.0"
|
||||
APIVersion = "2.6.1"
|
||||
// DBVersion is the version number of the Portainer database
|
||||
DBVersion = 30
|
||||
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
||||
|
||||
@@ -48,7 +48,7 @@ angular.module('portainer.docker').controller('LogViewerController', [
|
||||
};
|
||||
|
||||
this.downloadLogs = function () {
|
||||
const data = new Blob([_.reduce(this.state.filteredLogs, (acc, log) => acc + '\n' + log, '')]);
|
||||
const data = new Blob([_.reduce(this.state.filteredLogs, (acc, log) => acc + '\n' + log.line, '')]);
|
||||
FileSaver.saveAs(data, this.resourceName + '_logs.txt');
|
||||
};
|
||||
},
|
||||
|
||||
@@ -261,7 +261,7 @@ class KubernetesApplicationConverter {
|
||||
return res;
|
||||
}
|
||||
|
||||
static applicationToFormValues(app, resourcePools, configurations, persistentVolumeClaims, nodesLabels) {
|
||||
static applicationToFormValues(app, resourcePools, configurations, persistentVolumeClaims, nodesLabels, ingresses) {
|
||||
const res = new KubernetesApplicationFormValues();
|
||||
res.ApplicationType = app.ApplicationType;
|
||||
res.ResourcePool = _.find(resourcePools, ['Namespace.Name', app.ResourcePool]);
|
||||
@@ -278,7 +278,7 @@ class KubernetesApplicationConverter {
|
||||
res.PersistedFolders = KubernetesApplicationHelper.generatePersistedFoldersFormValuesFromPersistedFolders(app.PersistedFolders, persistentVolumeClaims); // generate from PVC and app.PersistedFolders
|
||||
res.Configurations = KubernetesApplicationHelper.generateConfigurationFormValuesFromEnvAndVolumes(app.Env, app.ConfigurationVolumes, configurations);
|
||||
res.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(app.AutoScaler, res.ReplicaCount);
|
||||
res.PublishedPorts = KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(app.ServiceType, app.PublishedPorts);
|
||||
res.PublishedPorts = KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(app.ServiceType, app.PublishedPorts, ingresses);
|
||||
res.Containers = app.Containers;
|
||||
|
||||
const isIngress = _.filter(res.PublishedPorts, (p) => p.IngressName).length;
|
||||
|
||||
@@ -274,7 +274,7 @@ class KubernetesApplicationHelper {
|
||||
/* #endregion */
|
||||
|
||||
/* #region PUBLISHED PORTS FV <> PUBLISHED PORTS */
|
||||
static generatePublishedPortsFormValuesFromPublishedPorts(serviceType, publishedPorts) {
|
||||
static generatePublishedPortsFormValuesFromPublishedPorts(serviceType, publishedPorts, ingress) {
|
||||
const generatePort = (port, rule) => {
|
||||
const res = new KubernetesApplicationPublishedPortFormValue();
|
||||
res.IsNew = false;
|
||||
@@ -282,6 +282,7 @@ class KubernetesApplicationHelper {
|
||||
res.IngressName = rule.IngressName;
|
||||
res.IngressRoute = rule.Path;
|
||||
res.IngressHost = rule.Host;
|
||||
res.IngressHosts = ingress.find((i) => i.Name === rule.IngressName).Hosts;
|
||||
}
|
||||
res.Protocol = port.Protocol;
|
||||
res.ContainerPort = port.TargetPort;
|
||||
|
||||
@@ -171,7 +171,7 @@ export class KubernetesIngressConverter {
|
||||
res.spec.rules = [];
|
||||
_.forEach(data.Hosts, (host) => {
|
||||
if (!host.NeedsDeletion) {
|
||||
res.spec.rules.push({ host: host.Host });
|
||||
res.spec.rules.push({ host: host.Host || host });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -124,6 +124,7 @@ export function KubernetesApplicationPublishedPortFormValue() {
|
||||
IngressName: undefined,
|
||||
IngressRoute: undefined,
|
||||
IngressHost: undefined,
|
||||
IngressHosts: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1368,7 +1368,7 @@
|
||||
class="form-control"
|
||||
name="ingress_hostname_{{ $index }}"
|
||||
ng-model="publishedPort.IngressHost"
|
||||
ng-options="host as (host | kubernetesApplicationIngressEmptyHostname) for host in ctrl.ingressHostnames"
|
||||
ng-options="host as (host | kubernetesApplicationIngressEmptyHostname) for host in publishedPort.IngressHosts"
|
||||
ng-change="ctrl.onChangePublishedPorts()"
|
||||
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
|
||||
>
|
||||
|
||||
@@ -321,6 +321,7 @@ class KubernetesCreateApplicationController {
|
||||
const ingresses = this.filteredIngresses;
|
||||
p.IngressName = ingresses && ingresses.length ? ingresses[0].Name : undefined;
|
||||
p.IngressHost = ingresses && ingresses.length ? ingresses[0].Hosts[0] : undefined;
|
||||
p.IngressHosts = ingresses && ingresses.length ? ingresses[0].Hosts : undefined;
|
||||
if (this.formValues.PublishedPorts.length) {
|
||||
p.Protocol = this.formValues.PublishedPorts[0].Protocol;
|
||||
}
|
||||
@@ -388,6 +389,7 @@ class KubernetesCreateApplicationController {
|
||||
onChangePortMappingIngress(index) {
|
||||
const publishedPort = this.formValues.PublishedPorts[index];
|
||||
const ingress = _.find(this.filteredIngresses, { Name: publishedPort.IngressName });
|
||||
publishedPort.IngressHosts = ingress.Hosts;
|
||||
this.ingressHostnames = ingress.Hosts;
|
||||
publishedPort.IngressHost = this.ingressHostnames.length ? this.ingressHostnames[0] : [];
|
||||
this.onChangePublishedPorts();
|
||||
@@ -972,7 +974,8 @@ class KubernetesCreateApplicationController {
|
||||
this.resourcePools,
|
||||
this.configurations,
|
||||
this.persistentVolumeClaims,
|
||||
this.nodesLabels
|
||||
this.nodesLabels,
|
||||
this.filteredIngresses
|
||||
);
|
||||
this.formValues.OriginalIngresses = this.filteredIngresses;
|
||||
this.savedFormValues = angular.copy(this.formValues);
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import { KEY_REGEX, VALUE_REGEX } from '@/portainer/helpers/env-vars';
|
||||
|
||||
class EnvironmentVariablesSimpleModeItemController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.KEY_REGEX = KEY_REGEX;
|
||||
this.VALUE_REGEX = VALUE_REGEX;
|
||||
}
|
||||
|
||||
onChangeName(name) {
|
||||
const fieldIsInvalid = typeof name === 'undefined';
|
||||
if (fieldIsInvalid) {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
placeholder="e.g. FOO"
|
||||
ng-model="$ctrl.variable.name"
|
||||
ng-disabled="$ctrl.variable.added"
|
||||
ng-pattern="$ctrl.KEY_REGEX"
|
||||
ng-change="$ctrl.onChangeName($ctrl.variable.name)"
|
||||
required
|
||||
/>
|
||||
@@ -36,7 +35,6 @@
|
||||
ng-model="$ctrl.variable.value"
|
||||
placeholder="e.g. bar"
|
||||
ng-trim="false"
|
||||
ng-pattern="$ctrl.VALUE_REGEX"
|
||||
name="value"
|
||||
ng-change="$ctrl.onChangeValue($ctrl.variable.value)"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import _ from 'lodash-es';
|
||||
|
||||
export const KEY_REGEX = /[a-zA-Z]([-_a-zA-Z0-9]*[a-zA-Z0-9])?/.source;
|
||||
|
||||
export const KEY_REGEX = /(.+)/.source;
|
||||
export const VALUE_REGEX = /(.*)?/.source;
|
||||
|
||||
const KEY_VALUE_REGEX = new RegExp(`^(${KEY_REGEX})\\s*=(${VALUE_REGEX})$`);
|
||||
@@ -16,7 +15,7 @@ export function parseDotEnvFile(src) {
|
||||
return parseArrayOfStrings(
|
||||
_.compact(src.split(NEWLINES_REGEX))
|
||||
.map((v) => v.trim())
|
||||
.filter((v) => !v.startsWith('#'))
|
||||
.filter((v) => !v.startsWith('#') && v !== '')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +39,7 @@ export function parseArrayOfStrings(array) {
|
||||
|
||||
const parsedKeyValArr = variableString.trim().match(KEY_VALUE_REGEX);
|
||||
if (parsedKeyValArr != null && parsedKeyValArr.length > 4) {
|
||||
return { name: parsedKeyValArr[1], value: parsedKeyValArr[3] || '' };
|
||||
return { name: parsedKeyValArr[1].trim(), value: parsedKeyValArr[3].trim() || '' };
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "2.6.0",
|
||||
"version": "2.6.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
||||
Reference in New Issue
Block a user