mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
AboutHttpService - forbid detailed info for non-local origin, introduce isOriginAllowed to ensure that access policy is clear
GitOrigin-RevId: 3e79479484eb5649560aae5f7033e32af413f987
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6d4521d74c
commit
b573f79a61
@@ -10,6 +10,7 @@ import com.intellij.openapi.application.ex.ApplicationInfoEx
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
|
||||
import com.intellij.util.PlatformUtils
|
||||
import com.intellij.util.io.isLocalOrigin
|
||||
import com.intellij.util.io.jackson.array
|
||||
import com.intellij.util.io.jackson.obj
|
||||
import com.intellij.util.io.origin
|
||||
@@ -47,58 +48,61 @@ import java.io.OutputStream
|
||||
internal class AboutHttpService : RestService() {
|
||||
override fun getServiceName() = "about"
|
||||
|
||||
override fun isHostTrusted(request: FullHttpRequest, urlDecoder: QueryStringDecoder): Boolean {
|
||||
return isTrustedOrigin(request) || super.isHostTrusted(request, urlDecoder)
|
||||
}
|
||||
|
||||
override fun isAccessible(request: HttpRequest): Boolean {
|
||||
return isTrustedOrigin(request) || super.isAccessible(request)
|
||||
override fun isOriginAllowed(request: HttpRequest): OriginCheckResult {
|
||||
val originAllowed = super.isOriginAllowed(request)
|
||||
if (originAllowed == OriginCheckResult.FORBID) {
|
||||
val origin = request.origin ?: return OriginCheckResult.FORBID
|
||||
@Suppress("SpellCheckingInspection")
|
||||
return if (origin.matches(Regex("https://([a-z0-9-]+\\.)*hyperskill.org$"))) OriginCheckResult.ALLOW else OriginCheckResult.FORBID
|
||||
}
|
||||
return originAllowed
|
||||
}
|
||||
|
||||
override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? {
|
||||
val byteOut = BufferExposingByteArrayOutputStream()
|
||||
writeApplicationInfoJson(byteOut, urlDecoder)
|
||||
writeApplicationInfoJson(byteOut, urlDecoder, request.isLocalOrigin())
|
||||
send(byteOut, request, context)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTrustedOrigin(request: HttpRequest): Boolean {
|
||||
val origin = request.origin ?: return false
|
||||
@Suppress("SpellCheckingInspection")
|
||||
return origin.matches("https?://([a-z0-9-]+\\.)*hyperskill.org$".toRegex())
|
||||
}
|
||||
fun writeApplicationInfoJson(out: OutputStream, urlDecoder: QueryStringDecoder?, isLocalOrigin: Boolean) {
|
||||
JsonFactory().createGenerator(out).useDefaultPrettyPrinter().use { writer ->
|
||||
writer.obj {
|
||||
writeAboutJson(writer)
|
||||
|
||||
fun writeApplicationInfoJson(out: OutputStream, urlDecoder: QueryStringDecoder?) {
|
||||
val writer = JsonFactory().createGenerator(out).useDefaultPrettyPrinter()
|
||||
writer.obj {
|
||||
writeAboutJson(writer)
|
||||
if (urlDecoder != null && getBooleanParameter("registeredFileTypes", urlDecoder)) {
|
||||
writer.array("registeredFileTypes") {
|
||||
for (fileType in FileTypeRegistry.getInstance().registeredFileTypes) {
|
||||
writer.obj {
|
||||
writer.writeStringField("name", fileType.name)
|
||||
writer.writeStringField("description", fileType.description)
|
||||
writer.writeBooleanField("isBinary", fileType.isBinary)
|
||||
// registeredFileTypes and more args are supported only for explicitly trusted origins
|
||||
if (!isLocalOrigin) {
|
||||
return
|
||||
}
|
||||
|
||||
if (urlDecoder != null && getBooleanParameter("registeredFileTypes", urlDecoder)) {
|
||||
writer.array("registeredFileTypes") {
|
||||
for (fileType in FileTypeRegistry.getInstance().registeredFileTypes) {
|
||||
writer.obj {
|
||||
writer.writeStringField("name", fileType.name)
|
||||
writer.writeStringField("description", fileType.description)
|
||||
writer.writeBooleanField("isBinary", fileType.isBinary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (urlDecoder != null && getBooleanParameter("more", urlDecoder)) {
|
||||
val appInfo = ApplicationInfoEx.getInstanceEx()
|
||||
writer.writeStringField("vendor", appInfo.companyName)
|
||||
writer.writeBooleanField("isEAP", appInfo.isEAP)
|
||||
writer.writeStringField("productCode", appInfo.build.productCode)
|
||||
writer.writeNumberField("buildDate", appInfo.buildDate.time.time)
|
||||
writer.writeBooleanField("isSnapshot", appInfo.build.isSnapshot)
|
||||
writer.writeStringField("configPath", PathManager.getConfigPath())
|
||||
writer.writeStringField("systemPath", PathManager.getSystemPath())
|
||||
writer.writeStringField("binPath", PathManager.getBinPath())
|
||||
writer.writeStringField("logPath", PathManager.getLogPath())
|
||||
writer.writeStringField("homePath", PathManager.getHomePath())
|
||||
|
||||
if (urlDecoder != null && getBooleanParameter("more", urlDecoder)) {
|
||||
val appInfo = ApplicationInfoEx.getInstanceEx()
|
||||
writer.writeStringField("vendor", appInfo.companyName)
|
||||
writer.writeBooleanField("isEAP", appInfo.isEAP)
|
||||
writer.writeStringField("productCode", appInfo.build.productCode)
|
||||
writer.writeNumberField("buildDate", appInfo.buildDate.time.time)
|
||||
writer.writeBooleanField("isSnapshot", appInfo.build.isSnapshot)
|
||||
writer.writeStringField("configPath", PathManager.getConfigPath())
|
||||
writer.writeStringField("systemPath", PathManager.getSystemPath())
|
||||
writer.writeStringField("binPath", PathManager.getBinPath())
|
||||
writer.writeStringField("logPath", PathManager.getLogPath())
|
||||
writer.writeStringField("homePath", PathManager.getHomePath())
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.close()
|
||||
}
|
||||
|
||||
fun writeAboutJson(writer: JsonGenerator) {
|
||||
|
||||
@@ -1,18 +1,4 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.ide;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
@@ -169,7 +155,7 @@ final class DiffHttpService extends RestService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccessible(@NotNull HttpRequest request) {
|
||||
return true;
|
||||
protected @NotNull OriginCheckResult isOriginAllowed(@NotNull HttpRequest request) {
|
||||
return OriginCheckResult.ASK_CONFIRMATION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.net.URISyntaxException
|
||||
internal class InstallPluginService : RestService() {
|
||||
override fun getServiceName() = "installPlugin"
|
||||
|
||||
override fun isAccessible(request: HttpRequest) = true
|
||||
override fun isOriginAllowed(request: HttpRequest) = OriginCheckResult.ASK_CONFIRMATION
|
||||
|
||||
var isAvailable = true
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ internal class OpenFileHttpService : RestService() {
|
||||
|
||||
override fun isMethodSupported(method: HttpMethod) = method === HttpMethod.GET || method === HttpMethod.POST
|
||||
|
||||
override fun isOriginAllowed(request: HttpRequest) = OriginCheckResult.ASK_CONFIRMATION
|
||||
|
||||
override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? {
|
||||
val keepAlive = HttpUtil.isKeepAlive(request)
|
||||
val channel = context.channel()
|
||||
@@ -170,8 +172,6 @@ internal class OpenFileHttpService : RestService() {
|
||||
session.launch()
|
||||
return mainTask.promise
|
||||
}
|
||||
|
||||
override fun isAccessible(request: HttpRequest) = true
|
||||
}
|
||||
|
||||
internal class OpenFileRequest {
|
||||
|
||||
@@ -59,7 +59,7 @@ final class ProjectSetRequestHandler extends RestService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccessible(@NotNull HttpRequest request) {
|
||||
return true;
|
||||
protected @NotNull OriginCheckResult isOriginAllowed(@NotNull HttpRequest request) {
|
||||
return OriginCheckResult.ASK_CONFIRMATION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ abstract class RestService : HttpRequestHandler() {
|
||||
@Throws(InterruptedException::class, InvocationTargetException::class)
|
||||
// e.g. upsource trust to configured host
|
||||
protected open fun isHostTrusted(request: FullHttpRequest): Boolean {
|
||||
if (request.isSignedRequest()) {
|
||||
if (request.isSignedRequest() || isOriginAllowed(request) == OriginCheckResult.ALLOW) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
package org.jetbrains.ide
|
||||
|
||||
import com.intellij.diagnostic.StartUpPerformanceService
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ex.ApplicationInfoEx
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.util.io.getHostName
|
||||
import com.intellij.util.io.origin
|
||||
import com.intellij.util.net.NetUtils
|
||||
import io.netty.buffer.Unpooled
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.handler.codec.http.FullHttpRequest
|
||||
@@ -15,24 +10,11 @@ import io.netty.handler.codec.http.HttpRequest
|
||||
import io.netty.handler.codec.http.QueryStringDecoder
|
||||
import org.jetbrains.io.response
|
||||
|
||||
private val LOG = logger<StartUpMeasurementService>()
|
||||
|
||||
internal class StartUpMeasurementService : RestService() {
|
||||
override fun getServiceName() = "startUpMeasurement"
|
||||
|
||||
override fun isAccessible(request: HttpRequest): Boolean {
|
||||
if (super.isAccessible(request)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// expose externally to use visualizer front-end
|
||||
// personal data is not exposed (but someone can say that 3rd plugin class names should be not exposed),
|
||||
// so, limit to dev builds only (EAP builds are not allowed too) or app in an internal mode (and still only for known hosts)
|
||||
return isTrustedHostName(request) && (ApplicationManager.getApplication().isInternal || ApplicationInfoEx.getInstanceEx().build.isSnapshot)
|
||||
}
|
||||
|
||||
override fun isHostTrusted(request: FullHttpRequest, urlDecoder: QueryStringDecoder): Boolean {
|
||||
return isTrustedHostName(request) || super.isHostTrusted(request, urlDecoder)
|
||||
override fun isOriginAllowed(request: HttpRequest): OriginCheckResult {
|
||||
return if (request.origin == "https://ij-perf.jetbrains.com") OriginCheckResult.ALLOW else super.isOriginAllowed(request)
|
||||
}
|
||||
|
||||
override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? {
|
||||
@@ -42,12 +24,4 @@ internal class StartUpMeasurementService : RestService() {
|
||||
sendResponse(request, context, response)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTrustedHostName(request: HttpRequest): Boolean {
|
||||
val hostName = getHostName(request) ?: return false
|
||||
if (!NetUtils.isLocalhost(hostName)) {
|
||||
LOG.error("Expected 'request.hostName' to be localhost. hostName=$hostName, origin=${request.origin}")
|
||||
}
|
||||
return hostName == "ij-perf.jetbrains.com" || hostName == "ij-perf.develar.org" || NetUtils.isLocalhost(hostName)
|
||||
}
|
||||
@@ -18,6 +18,12 @@ import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
abstract class HttpRequestHandler {
|
||||
enum class OriginCheckResult {
|
||||
ALLOW, FORBID,
|
||||
// any origin is allowed but user confirmation is required
|
||||
ASK_CONFIRMATION
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Your handler will be instantiated on first user request
|
||||
val EP_NAME = ExtensionPointName<HttpRequestHandler>("com.intellij.httpRequestHandler")
|
||||
@@ -45,10 +51,12 @@ abstract class HttpRequestHandler {
|
||||
val hostName = getHostName(request)
|
||||
// If attacker.com DNS rebound to 127.0.0.1 and user open site directly - no Origin or Referrer headers.
|
||||
// So we should check Host header.
|
||||
return hostName != null && isOriginAllowed(request) && isLocalHost(hostName)
|
||||
return hostName != null && isOriginAllowed(request) != OriginCheckResult.FORBID && isLocalHost(hostName)
|
||||
}
|
||||
|
||||
protected open fun isOriginAllowed(request: HttpRequest) = request.isLocalOrigin()
|
||||
protected open fun isOriginAllowed(request: HttpRequest): OriginCheckResult {
|
||||
return if (request.isLocalOrigin()) OriginCheckResult.ALLOW else OriginCheckResult.FORBID
|
||||
}
|
||||
|
||||
open fun isSupported(request: FullHttpRequest): Boolean {
|
||||
return request.method() === HttpMethod.GET || request.method() === HttpMethod.HEAD
|
||||
|
||||
Reference in New Issue
Block a user