Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Правило "Обращение к методам привилегированных модулей" #3172

Merged
merged 4 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/diagnostics/PrivilegedModuleMethodCall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Обращение к методам привилегированных модулей (PrivilegedModuleMethodCall)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Описание диагностики
<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->
При обращении к публичным процедурам и функциям привилегированных общих модулей могут нарушаться ограничения конфигурации по правам и ролям конфигурации, решения 1С.
Необходимо провалидировать подобные обращения для исключения обхода ограничений.

## Примеры
<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->
Например, в конфигурации существует привилегированный модуль выполнения заданий с именем `Задания`.
В этом модуле есть публичная функция `Функция ДобавитьЗадание(Знач ИмяМетода, Знач Параметры) Экспорт`.
Какой-то код конфигурации или расширения обращается к этому методу `Задания.ДобавитьЗадание("МетодДляВыполнения", Параметры);`

Необходимо проанализировать код и убедиться в том, что:
- указан правильный метод для выполнения задания - `МетодДляВыполнения`
- метод `МетодДляВыполнения` для выполнения задания не выполняет деструктивных действий
- и не выдает пользователям данные, запрещенные ограничениями конфигурации

## Источники
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников

* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->
16 changes: 16 additions & 0 deletions docs/en/diagnostics/PrivilegedModuleMethodCall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Accessing privileged module methods (PrivilegedModuleMethodCall)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Description
<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->

## Examples
<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->

## Sources
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников

* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2023
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import com.github._1c_syntax.bsl.languageserver.context.symbol.ModuleSymbol;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticParameter;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticScope;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType;
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex;
import com.github._1c_syntax.bsl.languageserver.references.model.Reference;
import com.github._1c_syntax.bsl.types.ModuleType;
import com.github._1c_syntax.mdclasses.mdo.MDCommonModule;
import lombok.RequiredArgsConstructor;
import org.eclipse.lsp4j.SymbolKind;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@DiagnosticMetadata(
type = DiagnosticType.SECURITY_HOTSPOT,
severity = DiagnosticSeverity.MAJOR,
minutesToFix = 60,
tags = {
DiagnosticTag.SUSPICIOUS
},
scope = DiagnosticScope.BSL
)
@RequiredArgsConstructor
public class PrivilegedModuleMethodCallDiagnostic extends AbstractDiagnostic {

private static final boolean VALIDATE_NESTED_CALLS = true;

@DiagnosticParameter(
type = Boolean.class,
defaultValue = "" + VALIDATE_NESTED_CALLS
)
private boolean validateNestedCalls = VALIDATE_NESTED_CALLS;

private final ReferenceIndex referenceIndex;
private List<ModuleSymbol> privilegedModuleSymbols = new ArrayList<>();

@Override
protected void check() {
if (privilegedModuleSymbols.isEmpty()){
privilegedModuleSymbols = getPrivilegedModuleSymbols();
}
if (privilegedModuleSymbols.isEmpty()){
return;
}

referenceIndex.getReferencesFrom(documentContext.getUri(), SymbolKind.Method).stream()
.filter(this::isReferenceToModules)
.forEach(this::fireIssue);
}

private List<ModuleSymbol> getPrivilegedModuleSymbols() {
return documentContext.getServerContext().getConfiguration().getCommonModules()
.values().stream()
.filter(MDCommonModule::isPrivileged)
.flatMap(mdCommonModule -> getPrivilegedModuleSymbol(mdCommonModule).stream())
.toList();
}

private Optional<ModuleSymbol> getPrivilegedModuleSymbol(MDCommonModule mdCommonModule) {
return documentContext.getServerContext().getDocument(
mdCommonModule.getMdoReference().getMdoRef(), ModuleType.CommonModule)
.map(documentContext1 -> documentContext1.getSymbolTree().getModule());
}

private boolean isReferenceToModules(Reference reference) {
if (!validateNestedCalls && reference.getUri().equals(documentContext.getUri())){
return false;
}
return reference.getSourceDefinedSymbol()
.flatMap(sourceDefinedSymbol -> sourceDefinedSymbol.getRootParent(SymbolKind.Module))
.filter(ModuleSymbol.class::isInstance)
.map(ModuleSymbol.class::cast)
.filter(privilegedModuleSymbols::contains)
.isPresent();
}

private void fireIssue(Reference reference) {
diagnosticStorage.addDiagnostic(reference.getSelectionRange(),
info.getMessage(reference.getSymbol().getName()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,24 @@
"title": "Source code parse error",
"$id": "#/definitions/ParseError"
},
"PrivilegedModuleMethodCall": {
"description": "Accessing privileged module methods",
"default": true,
"type": [
"boolean",
"object"
],
"title": "Accessing privileged module methods",
"properties": {
"validateNestedCalls": {
"description": "Validate nested method calls from privileged modules",
"default": true,
"type": "boolean",
"title": "Validate nested method calls from privileged modules"
}
},
"$id": "#/definitions/PrivilegedModuleMethodCall"
},
"ProcedureReturnsValue": {
"description": "Procedure should not return Value",
"default": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
diagnosticMessage=Check the %s method access of the privileged module
diagnosticName=Accessing privileged module methods
validateNestedCalls=Validate nested method calls from privileged modules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
diagnosticMessage=Проверьте обращение к методу %s привилегированного модуля
diagnosticName=Обращение к методам привилегированных модулей
validateNestedCalls=Проверять вложенные вызовы методов из привилегированных модулей
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2023
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import org.eclipse.lsp4j.Diagnostic;
import org.junit.jupiter.api.Test;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

import static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

class PrivilegedModuleMethodCallDiagnosticTest extends AbstractDiagnosticTest<PrivilegedModuleMethodCallDiagnostic> {
private static final String PATH_TO_METADATA = "src/test/resources/metadata/privilegedModules";
private static final String PATH_TO_MODULE_FILE = PATH_TO_METADATA + "/CommonModules/ПривилегированныйМодуль1/Ext/Module.bsl";

PrivilegedModuleMethodCallDiagnosticTest() {
super(PrivilegedModuleMethodCallDiagnostic.class);
}

@Test
void testWithoutMetadata() {
var diagnostics = getDiagnostics();
assertThat(diagnostics).isEmpty();
}

@Test
void test() {
initServerContext(PATH_TO_METADATA);

var diagnostics = getDiagnostics();

assertThat(diagnostics).hasSize(2);
assertThat(diagnostics, true)
.hasMessageOnRange("Проверьте обращение к методу ПубличнаяФункция привилегированного модуля", 3, 40, 56)
.hasMessageOnRange("Проверьте обращение к методу ПубличнаяПроцедура привилегированного модуля", 4, 29, 47);
}

@Test
void getNestedCalls() {
var diagnostics = getDiagnosticsAsCommonModule();
assertThat(diagnostics).hasSize(2);
assertThat(diagnostics, true)
.hasMessageOnRange("Проверьте обращение к методу ПубличнаяФункция привилегированного модуля", 15, 15, 31)
.hasMessageOnRange("Проверьте обращение к методу ПубличнаяПроцедура привилегированного модуля", 19, 4, 22);
}

@Test
void testParameterValidateNestedCalls() {
Map<String, Object> configuration = diagnosticInstance.getInfo().getDefaultConfiguration();
configuration.put("validateNestedCalls", false);
diagnosticInstance.configure(configuration);

var diagnostics = getDiagnosticsAsCommonModule();
assertThat(diagnostics).isEmpty();
}

private List<Diagnostic> getDiagnosticsAsCommonModule() {
Path moduleFile = Paths.get(PATH_TO_MODULE_FILE).toAbsolutePath();

initServerContext(PATH_TO_METADATA);

var documentContext = spy(getDocumentContext(diagnosticInstance.getClass().getSimpleName()));
when(documentContext.getUri()).thenReturn(moduleFile.toUri());

return getDiagnostics(documentContext);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#Область ПрограммныйИнтерфейс

Функция Тест1()
Значение = ПривилегированныйМодуль1.ПубличнаяФункция(); // ошибка
ПривилегированныйМодуль1.ПубличнаяПроцедура(); // ошибка
КонецФункции

Процедура Тест2()
Значение = ПривилегированныйМодуль1.ПриватнаяФункция(); // не ошибка в данном правиле
ПривилегированныйМодуль1.ПриватнаяПроцедура(); // не ошибка в данном правиле
КонецПроцедуры

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

#КонецОбласти
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.11">
<CommonModule uuid="8bec7488-90ab-484a-bdc9-ce1584d95925">
<Properties>
<Name>ОбщийМодуль2</Name>
<Synonym/>
<Comment/>
<Global>false</Global>
<ClientManagedApplication>false</ClientManagedApplication>
<Server>true</Server>
<ExternalConnection>false</ExternalConnection>
<ClientOrdinaryApplication>false</ClientOrdinaryApplication>
<ServerCall>false</ServerCall>
<Privileged>false</Privileged>
<ReturnValuesReuse>DontUse</ReturnValuesReuse>
</Properties>
</CommonModule>
</MetaDataObject>
Loading
Loading