Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c1977e0aa | ||
|
|
c47fd9f9ed | ||
|
|
767d1d1970 | ||
|
|
ef81e5c0e0 | ||
|
|
234b7a3d5e | ||
|
|
af49305e64 | ||
|
|
d181d1251c | ||
|
|
5f7db66e95 | ||
|
|
17378bdef6 | ||
|
|
010542ac1e |
@@ -910,7 +910,7 @@
|
||||
],
|
||||
"version": {
|
||||
"DB_UPDATING": "false",
|
||||
"DB_VERSION": "51",
|
||||
"DB_VERSION": "52",
|
||||
"INSTANCE_ID": "null"
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// @title PortainerCE API
|
||||
// @version 2.14.1
|
||||
// @version 2.14.2
|
||||
// @description.markdown api-description.md
|
||||
// @termsOfService
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package helm
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/portainer/libhelm"
|
||||
@@ -108,7 +107,7 @@ func (handler *Handler) getHelmClusterAccess(r *http.Request) (*options.Kubernet
|
||||
|
||||
hostURL := "localhost"
|
||||
if !sslSettings.SelfSigned {
|
||||
hostURL = strings.Split(r.Host, ":")[0]
|
||||
hostURL = r.Host
|
||||
}
|
||||
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(hostURL, endpoint.ID)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
@@ -145,8 +144,7 @@ func (handler *Handler) buildConfig(r *http.Request, tokenData *portainer.TokenD
|
||||
}
|
||||
|
||||
func (handler *Handler) buildCluster(r *http.Request, endpoint portainer.Endpoint) clientV1.NamedCluster {
|
||||
hostURL := strings.Split(r.Host, ":")[0]
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(hostURL, endpoint.ID)
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(r.Host, endpoint.ID)
|
||||
return clientV1.NamedCluster{
|
||||
Name: buildClusterName(endpoint.Name),
|
||||
Cluster: clientV1.Cluster{
|
||||
|
||||
@@ -95,8 +95,10 @@ func generatePublicSettings(appSettings *portainer.Settings) *publicSettingsResp
|
||||
}
|
||||
}
|
||||
//if LDAP authentication is on, compose the related fields from application settings
|
||||
if publicSettings.AuthenticationMethod == portainer.AuthenticationLDAP {
|
||||
publicSettings.TeamSync = len(appSettings.LDAPSettings.GroupSearchSettings) > 0
|
||||
if publicSettings.AuthenticationMethod == portainer.AuthenticationLDAP && appSettings.LDAPSettings.GroupSearchSettings != nil {
|
||||
if len(appSettings.LDAPSettings.GroupSearchSettings) > 0 {
|
||||
publicSettings.TeamSync = len(appSettings.LDAPSettings.GroupSearchSettings[0].GroupBaseDN) > 0
|
||||
}
|
||||
}
|
||||
return publicSettings
|
||||
}
|
||||
|
||||
@@ -81,11 +81,11 @@ func FilterRegistries(registries []portainer.Registry, user *portainer.User, tea
|
||||
}
|
||||
|
||||
// FilterEndpoints filters environments(endpoints) based on user role and team memberships.
|
||||
// Non administrator and non-team-leader only have access to authorized environments(endpoints) (can be inherited via endpoint groups).
|
||||
// Non administrator only have access to authorized environments(endpoints) (can be inherited via endpoint groups).
|
||||
func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.Endpoint {
|
||||
filteredEndpoints := endpoints
|
||||
|
||||
if !context.IsAdmin && !context.IsTeamLeader {
|
||||
if !context.IsAdmin {
|
||||
filteredEndpoints = make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
@@ -101,11 +101,11 @@ func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.Endpoint
|
||||
}
|
||||
|
||||
// FilterEndpointGroups filters environment(endpoint) groups based on user role and team memberships.
|
||||
// Non administrator users and Non-team-leaders only have access to authorized environment(endpoint) groups.
|
||||
// Non administrator users only have access to authorized environment(endpoint) groups.
|
||||
func FilterEndpointGroups(endpointGroups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.EndpointGroup {
|
||||
filteredEndpointGroups := endpointGroups
|
||||
|
||||
if !context.IsAdmin && !context.IsTeamLeader {
|
||||
if !context.IsAdmin {
|
||||
filteredEndpointGroups = make([]portainer.EndpointGroup, 0)
|
||||
|
||||
for _, group := range endpointGroups {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// KubeClusterAccessService represents a service that is responsible for centralizing kube cluster access data
|
||||
@@ -94,11 +95,20 @@ func (service *kubeClusterAccessService) IsSecure() bool {
|
||||
// - pass down params to binaries
|
||||
func (service *kubeClusterAccessService) GetData(hostURL string, endpointID portainer.EndpointID) kubernetesClusterAccessData {
|
||||
baseURL := service.baseURL
|
||||
|
||||
// When the api call is internal, the baseURL should not be used.
|
||||
if hostURL == "localhost" {
|
||||
hostURL = hostURL + service.httpsBindAddr
|
||||
baseURL = "/"
|
||||
}
|
||||
|
||||
if baseURL != "/" {
|
||||
baseURL = fmt.Sprintf("/%s/", strings.Trim(baseURL, "/"))
|
||||
}
|
||||
|
||||
clusterURL := hostURL + service.httpsBindAddr + baseURL
|
||||
logrus.Infof("[kubeconfig] [hostURL: %s, httpsBindAddr: %s, baseURL: %s]", hostURL, service.httpsBindAddr, baseURL)
|
||||
|
||||
clusterURL := hostURL + baseURL
|
||||
|
||||
clusterServerURL := fmt.Sprintf("https://%sapi/endpoints/%d/kubernetes", clusterURL, endpointID)
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestKubeClusterAccessService_GetKubeConfigInternal(t *testing.T) {
|
||||
clusterAccessDetails := kcs.GetData("mysite.com", 1)
|
||||
|
||||
wantClusterAccessDetails := kubernetesClusterAccessData{
|
||||
ClusterServerURL: "https://mysite.com:9443/api/endpoints/1/kubernetes",
|
||||
ClusterServerURL: "https://mysite.com/api/endpoints/1/kubernetes",
|
||||
CertificateAuthorityFile: "",
|
||||
CertificateAuthorityData: "",
|
||||
}
|
||||
|
||||
@@ -1385,9 +1385,9 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "2.14.1"
|
||||
APIVersion = "2.14.2"
|
||||
// DBVersion is the version number of the Portainer database
|
||||
DBVersion = 51
|
||||
DBVersion = 52
|
||||
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
||||
ComposeSyntaxMaxVersion = "3.9"
|
||||
// AssetsServerURL represents the URL of the Portainer asset server
|
||||
|
||||
@@ -5,7 +5,7 @@ angular.module('portainer.kubernetes').factory('HelmFactory', HelmFactory);
|
||||
/* @ngInject */
|
||||
function HelmFactory($resource, API_ENDPOINT_ENDPOINTS) {
|
||||
const helmUrl = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/helm';
|
||||
const templatesUrl = '/api/templates/helm';
|
||||
const templatesUrl = 'api/templates/helm';
|
||||
|
||||
return $resource(
|
||||
helmUrl,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
loaded="$ctrl.loaded"
|
||||
page-type="$ctrl.pageType"
|
||||
table-type="available"
|
||||
retrieve-page="$ctrl.getPaginatedEndpoints"
|
||||
retrieve-page="$ctrl.getAvailableEndpoints"
|
||||
dataset="$ctrl.endpoints.available"
|
||||
entry-click="$ctrl.associateEndpoint"
|
||||
pagination-state="$ctrl.state.available"
|
||||
@@ -34,7 +34,7 @@
|
||||
loaded="$ctrl.loaded"
|
||||
page-type="$ctrl.pageType"
|
||||
table-type="associated"
|
||||
retrieve-page="$ctrl.getPaginatedEndpoints"
|
||||
retrieve-page="$ctrl.getAssociatedEndpoints"
|
||||
dataset="$ctrl.endpoints.associated"
|
||||
entry-click="$ctrl.dissociateEndpoint"
|
||||
pagination-state="$ctrl.state.associated"
|
||||
|
||||
@@ -27,7 +27,7 @@ class AssoicatedEndpointsSelectorController {
|
||||
available: null,
|
||||
};
|
||||
|
||||
this.getEndpoints = this.getEndpoints.bind(this);
|
||||
this.getAvailableEndpoints = this.getAvailableEndpoints.bind(this);
|
||||
this.getEndpointsAsync = this.getEndpointsAsync.bind(this);
|
||||
this.getAssociatedEndpoints = this.getAssociatedEndpoints.bind(this);
|
||||
this.getAssociatedEndpointsAsync = this.getAssociatedEndpointsAsync.bind(this);
|
||||
@@ -47,10 +47,10 @@ class AssoicatedEndpointsSelectorController {
|
||||
|
||||
loadData() {
|
||||
this.getAssociatedEndpoints();
|
||||
this.getEndpoints();
|
||||
this.getAvailableEndpoints();
|
||||
}
|
||||
|
||||
getEndpoints() {
|
||||
getAvailableEndpoints() {
|
||||
return this.$async(this.getEndpointsAsync);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,22 +22,3 @@ export function withFileSize(fileValidation: FileSchema, maxSize: number) {
|
||||
return file.size <= maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
export function withFileType(
|
||||
fileValidation: FileSchema,
|
||||
fileTypes: File['type'][]
|
||||
) {
|
||||
return fileValidation.test(
|
||||
'file-type',
|
||||
'Selected file has unsupported format.',
|
||||
validateFileType
|
||||
);
|
||||
|
||||
function validateFileType(file?: File) {
|
||||
if (!file) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return fileTypes.includes(file.type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,7 @@
|
||||
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||
<input type="password" class="form-control" ng-model="formValues.confirmPassword" id="confirm_password" />
|
||||
<span class="input-group-addon"
|
||||
><i
|
||||
ng-class="
|
||||
{ true: 'fa fa-check green-icon', false: 'fa fa-times red-icon' }[
|
||||
form.new_password.$viewValue !== '' && form.new_password.$viewValue === formValues.confirmPassword
|
||||
]
|
||||
"
|
||||
aria-hidden="true"
|
||||
></i
|
||||
><i ng-class="{ true: 'fa fa-check green-icon', false: 'fa fa-times red-icon' }[formValues.newPassword === formValues.confirmPassword]" aria-hidden="true"></i
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,7 +60,7 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="isDemoUser || (AuthenticationMethod !== 1 && !initialUser) || !formValues.currentPassword || !formValues.newPassword || !formValues.confirmPassword || form.$invalid || form.new_password.$viewValue !== formValues.confirmPassword"
|
||||
ng-disabled="isDemoUser || (AuthenticationMethod !== 1 && !isInitialAdmin) || !formValues.currentPassword || !formValues.newPassword || form.$invalid || formValues.newPassword !== formValues.confirmPassword"
|
||||
ng-click="updatePassword()"
|
||||
>
|
||||
Update password
|
||||
@@ -75,11 +68,11 @@
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-click="skipPasswordChange()" ng-if="forceChangePassword && timesPasswordChangeSkipped < 2"
|
||||
>Remind me later</button
|
||||
>
|
||||
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 2 && !initialUser">
|
||||
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 2 && !isInitialAdmin">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
You cannot change your password when using LDAP authentication.
|
||||
</span>
|
||||
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 3 && !initialUser">
|
||||
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 3 && !isInitialAdmin">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
You cannot change your password when using OAuth authentication.
|
||||
</span>
|
||||
|
||||
@@ -118,6 +118,7 @@ angular.module('portainer.app').controller('AccountController', [
|
||||
$scope.userID = userDetails.ID;
|
||||
$scope.userRole = Authentication.getUserDetails().role;
|
||||
$scope.forceChangePassword = userDetails.forceChangePassword;
|
||||
$scope.isInitialAdmin = userDetails.ID === 1;
|
||||
|
||||
if (state.application.demoEnvironment.enabled) {
|
||||
$scope.isDemoUser = state.application.demoEnvironment.users.includes($scope.userID);
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
label="'Use custom logo'"
|
||||
value="formValues.customLogo"
|
||||
checked="formValues.customLogo"
|
||||
name="'toggle_logo'"
|
||||
disabled="state.isDemo"
|
||||
on-change="(onToggleCustomLogo)"
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
label="'Allow the collection of anonymous statistics'"
|
||||
value="formValues.enableTelemetry"
|
||||
checked="formValues.enableTelemetry"
|
||||
name="'toggle_enableTelemetry'"
|
||||
on-change="(onToggleEnableTelemetry)"
|
||||
disabled="state.isDemo"
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
children-paths="['portainer.users.user' ,'portainer.teams' ,'portainer.teams.team' ,'portainer.roles' ,'portainer.roles.role' ,'portainer.roles.new']"
|
||||
>
|
||||
<sidebar-menu-item path="portainer.teams" class-name="sidebar-sublist" data-cy="portainerSidebar-teams" title="Teams">Teams</sidebar-menu-item>
|
||||
<sidebar-menu-item path="portainer.roles" class-name="sidebar-sublist" data-cy="portainerSidebar-roles" title="Roles">Roles</sidebar-menu-item>
|
||||
<sidebar-menu-item ng-if="isAdmin" path="portainer.roles" class-name="sidebar-sublist" data-cy="portainerSidebar-roles" title="Roles">Roles</sidebar-menu-item>
|
||||
</sidebar-menu>
|
||||
|
||||
<div ng-if="isAdmin">
|
||||
|
||||
@@ -38,12 +38,15 @@ export function intersectVariables(
|
||||
) {
|
||||
const oldVariablesWithLabel = oldVariables.filter((v) => !!v.label);
|
||||
|
||||
return [
|
||||
...oldVariablesWithLabel,
|
||||
...newVariables.filter(
|
||||
(v) => !oldVariablesWithLabel.find(({ name }) => name === v.name)
|
||||
),
|
||||
];
|
||||
return _.uniqBy(
|
||||
[
|
||||
...oldVariablesWithLabel,
|
||||
...newVariables.filter(
|
||||
(v) => !oldVariablesWithLabel.find(({ name }) => name === v.name)
|
||||
),
|
||||
],
|
||||
'name'
|
||||
);
|
||||
}
|
||||
|
||||
export function renderTemplate(
|
||||
@@ -68,5 +71,5 @@ export function renderTemplate(
|
||||
)
|
||||
);
|
||||
|
||||
return Mustache.render(template, state);
|
||||
return Mustache.render(template, state, undefined, { escape: (t) => t });
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@ import { useFormikContext } from 'formik';
|
||||
import { FileUploadField } from '@/portainer/components/form-components/FileUpload';
|
||||
import { SwitchField } from '@/portainer/components/form-components/SwitchField';
|
||||
import { FormControl } from '@/portainer/components/form-components/FormControl';
|
||||
import {
|
||||
file,
|
||||
withFileSize,
|
||||
withFileType,
|
||||
} from '@/portainer/helpers/yup-file-validation';
|
||||
import { file, withFileSize } from '@/portainer/helpers/yup-file-validation';
|
||||
|
||||
import { FormValues } from './types';
|
||||
|
||||
@@ -82,16 +78,9 @@ export function TLSFieldset() {
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 5_242_880; // 5MB
|
||||
const ALLOWED_FILE_TYPES = [
|
||||
'application/x-x509-ca-cert',
|
||||
'application/x-pem-file',
|
||||
];
|
||||
|
||||
function certValidation() {
|
||||
return withFileType(
|
||||
withFileSize(file(), MAX_FILE_SIZE),
|
||||
ALLOWED_FILE_TYPES
|
||||
).when(['tls', 'skipVerify'], {
|
||||
return withFileSize(file(), MAX_FILE_SIZE).when(['tls', 'skipVerify'], {
|
||||
is: (tls: boolean, skipVerify: boolean) => tls && !skipVerify,
|
||||
then: (schema) => schema.required('File is required'),
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "2.14.1",
|
||||
"version": "2.14.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
||||
Reference in New Issue
Block a user