cleanup, JetBrains Academy site as trusted origin for about service

GitOrigin-RevId: 5fd1f46b931d698a3e3d54330757cae3bb87ec81
This commit is contained in:
Vladimir Krivosheev
2020-06-08 20:17:54 +03:00
committed by intellij-monorepo-bot
parent 60ae273374
commit 8a4659f30d
8 changed files with 104 additions and 112 deletions
@@ -27,5 +27,6 @@
<orderEntry type="library" name="netty-codec-http" level="project" />
<orderEntry type="library" name="fastutil-min" level="project" />
<orderEntry type="library" name="Trove4j" level="project" />
<orderEntry type="library" name="jackson" level="project" />
</component>
</module>
@@ -66,7 +66,7 @@ class BuiltInWebServer : HttpRequestHandler() {
override fun isSupported(request: FullHttpRequest): Boolean = super.isSupported(request) || request.method() == HttpMethod.POST
override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean {
var hostName = request.hostName ?: return false
var hostName = getHostName(request) ?: return false
val projectName: String?
val isIpv6 = hostName[0] == '[' && hostName.length > 2 && hostName[hostName.length - 1] == ']'
if (isIpv6) {
@@ -1,21 +1,24 @@
// Copyright 2000-2019 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;
// 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.JsonWriter;
import com.intellij.ide.IdeAboutInfoUtil;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonGenerator
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.application.PathManager
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.jackson.array
import com.intellij.util.io.jackson.obj
import com.intellij.util.io.origin
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.FullHttpRequest
import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.codec.http.QueryStringDecoder
import org.jetbrains.ide.RestService.Companion.getBooleanParameter
import java.io.OutputStream
/**
* @api {get} /about The application info
@@ -35,61 +38,83 @@ import java.io.OutputStream;
* @apiSuccess {String} registeredFileTypes.description The user-readable description of the file type.
* @apiSuccess {Boolean} registeredFileTypes.isBinary Whether files of the specified type contain binary data.
*
* * @apiExample Request-Example:
* * @apiExample Request-Example:
* /rest/about?registeredFileTypes
*
* @apiUse SuccessExample
* @apiUse SuccessExampleWithRegisteredFileTypes
*/
public class AboutHttpService extends RestService {
@NotNull
@Override
protected String getServiceName() {
return "about";
internal class AboutHttpService : RestService() {
override fun getServiceName() = "about"
override fun isHostTrusted(request: FullHttpRequest, urlDecoder: QueryStringDecoder): Boolean {
return isTrustedOrigin(request) || super.isHostTrusted(request, urlDecoder)
}
@Nullable
@Override
public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
BufferExposingByteArrayOutputStream byteOut = new BufferExposingByteArrayOutputStream();
getAbout(byteOut, urlDecoder);
send(byteOut, request, context);
return null;
override fun isAccessible(request: HttpRequest): Boolean {
return isTrustedOrigin(request) || super.isAccessible(request)
}
public static void getAbout(@NotNull OutputStream out, @Nullable QueryStringDecoder urlDecoder) throws IOException {
JsonWriter writer = createJsonWriter(out);
writer.beginObject();
IdeAboutInfoUtil.writeAboutJson(writer);
if (urlDecoder != null && getBooleanParameter("registeredFileTypes", urlDecoder)) {
writer.name("registeredFileTypes").beginArray();
for (FileType fileType : FileTypeRegistry.getInstance().getRegisteredFileTypes()) {
writer.beginObject();
writer.name("name").value(fileType.getName());
writer.name("description").value(fileType.getDescription());
writer.name("isBinary").value(fileType.isBinary());
writer.endObject();
}
writer.endArray();
}
if (urlDecoder != null && getBooleanParameter("more", urlDecoder)) {
ApplicationInfoEx appInfo = ApplicationInfoEx.getInstanceEx();
writer.name("vendor").value(appInfo.getCompanyName());
writer.name("isEAP").value(appInfo.isEAP());
writer.name("productCode").value(appInfo.getBuild().getProductCode());
writer.name("buildDate").value(appInfo.getBuildDate().getTime().getTime());
writer.name("isSnapshot").value(appInfo.getBuild().isSnapshot());
writer.name("configPath").value(PathManager.getConfigPath());
writer.name("systemPath").value(PathManager.getSystemPath());
writer.name("binPath").value(PathManager.getBinPath());
writer.name("logPath").value(PathManager.getLogPath());
writer.name("homePath").value(PathManager.getHomePath());
}
writer.endObject();
writer.close();
override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? {
val byteOut = BufferExposingByteArrayOutputStream()
writeApplicationInfoJson(byteOut, urlDecoder)
send(byteOut, request, context)
return null
}
}
private fun isTrustedOrigin(request: HttpRequest): Boolean {
val origin = request.origin ?: return false
@Suppress("SpellCheckingInspection")
return origin == "hyperskill.org"
}
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)
}
}
}
}
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) {
var appName = ApplicationInfoEx.getInstanceEx().fullApplicationName
if (!PlatformUtils.isIdeaUltimate()) {
val productName = ApplicationNamesInfo.getInstance().productName
appName = appName
.replace("$productName ($productName)", productName)
.removePrefix("JetBrains ")
}
writer.writeStringField("name", appName)
writer.writeStringField("productName", ApplicationNamesInfo.getInstance().productName)
val build = ApplicationInfo.getInstance().build
writer.writeNumberField("baselineVersion", build.baselineVersion)
if (!build.isSnapshot) {
writer.writeStringField("buildNumber", build.asStringWithoutProductCode())
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2019 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.
// 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.intellij.ide.plugins.marketplace.MarketplaceRequests
@@ -10,12 +10,11 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PluginsAdvertiser
import com.intellij.openapi.util.BuildNumber
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
import com.intellij.openapi.util.text.StringUtil
import com.intellij.ui.AppIcon
import com.intellij.util.PlatformUtils
import com.intellij.util.io.hostName
import com.intellij.util.io.getHostName
import com.intellij.util.io.origin
import com.intellij.util.net.NetUtils
import com.intellij.util.text.nullize
@@ -127,7 +126,7 @@ internal class InstallPluginService : RestService() {
return false
}
val hostName = request.hostName
val hostName = getHostName(request)
if (hostName != null && !NetUtils.isLocalhost(hostName)) {
LOG.error("Expected 'request.hostName' to be localhost. hostName='$hostName', origin='$origin'")
}
@@ -1,11 +1,11 @@
// Copyright 2000-2019 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.
// 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.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.hostName
import com.intellij.util.io.getHostName
import com.intellij.util.io.origin
import com.intellij.util.net.NetUtils
import io.netty.buffer.Unpooled
@@ -45,7 +45,7 @@ internal class StartUpMeasurementService : RestService() {
}
private fun isTrustedHostName(request: HttpRequest): Boolean {
val hostName = request.hostName ?: return false
val hostName = getHostName(request) ?: return false
if (!NetUtils.isLocalhost(hostName)) {
LOG.error("Expected 'request.hostName' to be localhost. hostName=$hostName, origin=${request.origin}")
}
@@ -1,32 +0,0 @@
// 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 com.intellij.ide;
import com.google.gson.stream.JsonWriter;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.PlatformUtils;
import java.io.IOException;
public final class IdeAboutInfoUtil {
public static void writeAboutJson(JsonWriter writer) throws IOException {
String appName = ApplicationInfoEx.getInstanceEx().getFullApplicationName();
BuildNumber build = ApplicationInfo.getInstance().getBuild();
if (!PlatformUtils.isIdeaUltimate()) {
String productName = ApplicationNamesInfo.getInstance().getProductName();
appName = appName.replace(productName + " (" + productName + ")", productName);
appName = StringUtil.trimStart(appName, "JetBrains ");
}
writer.name("name").value(appName);
writer.name("productName").value(ApplicationNamesInfo.getInstance().getProductName());
writer.name("baselineVersion").value(build.getBaselineVersion());
if (!build.isSnapshot()) {
writer.name("buildNumber").value(build.asStringWithoutProductCode());
}
}
}
@@ -197,12 +197,11 @@ val Channel.uriScheme: String
val HttpRequest.host: String?
get() = headers().getAsString(HttpHeaderNames.HOST)
val HttpRequest.hostName: String?
get() {
val hostAndPort = headers().getAsString(HttpHeaderNames.HOST)?.ifBlank { null } ?: return null
val portIndex = hostAndPort.lastIndexOf(':')
return if (portIndex > 0) hostAndPort.substring(0, portIndex).ifBlank { null } else hostAndPort
}
fun getHostName(httpRequest: HttpRequest): String? {
val hostAndPort = httpRequest.headers().getAsString(HttpHeaderNames.HOST)?.ifBlank { null } ?: return null
val portIndex = hostAndPort.lastIndexOf(':')
return if (portIndex > 0) hostAndPort.substring(0, portIndex).ifBlank { null } else hostAndPort
}
val HttpRequest.origin: String?
get() = headers().getAsString(HttpHeaderNames.ORIGIN)
@@ -2,7 +2,7 @@
package org.jetbrains.ide
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.util.io.hostName
import com.intellij.util.io.getHostName
import com.intellij.util.io.isLocalHost
import com.intellij.util.io.isLocalOrigin
import io.netty.channel.Channel
@@ -42,7 +42,7 @@ abstract class HttpRequestHandler {
*/
@SuppressWarnings("SpellCheckingInspection")
open fun isAccessible(request: HttpRequest): Boolean {
val hostName = request.hostName
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 && request.isLocalOrigin() && isLocalHost(hostName)