Skip to content

Commit

Permalink
Merge pull request #3250 from scala-steward-org/topic/do-not-cache-cr…
Browse files Browse the repository at this point in the history
…edentials

Do not cache the output of `gitAskPass`
  • Loading branch information
fthomas authored Dec 23, 2023
2 parents 07a4410 + d23c691 commit 556eb5c
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,13 @@
package org.scalasteward.core.application

import better.files.File
import cats.Monad
import cats.syntax.all._
import org.http4s.Uri
import org.http4s.Uri.UserInfo
import org.scalasteward.core.application.Cli.EnvVar
import org.scalasteward.core.application.Config._
import org.scalasteward.core.data.Resolver
import org.scalasteward.core.forge.ForgeType
import org.scalasteward.core.forge.data.AuthenticatedUser
import org.scalasteward.core.forge.github.GitHubApp
import org.scalasteward.core.git.Author
import org.scalasteward.core.io.{ProcessAlg, WorkspaceAlg}
import org.scalasteward.core.util
import org.scalasteward.core.util.Nel
import scala.concurrent.duration.FiniteDuration

Expand Down Expand Up @@ -71,20 +65,6 @@ final case class Config(
defaultResolver: Resolver,
refreshBackoffPeriod: FiniteDuration
) {
def forgeUser[F[_]](implicit
processAlg: ProcessAlg[F],
workspaceAlg: WorkspaceAlg[F],
F: Monad[F]
): F[AuthenticatedUser] =
for {
rootDir <- workspaceAlg.rootDir
urlWithUser = util.uri.withUserInfo
.replace(UserInfo(forgeCfg.login, None))(forgeCfg.apiHost)
.renderString
prompt = s"Password for '$urlWithUser': "
password <- processAlg.exec(Nel.of(gitCfg.gitAskPass.pathAsString, prompt), rootDir)
} yield AuthenticatedUser(forgeCfg.login, password.mkString.trim)

def forgeSpecificCfg: ForgeSpecificCfg =
forgeCfg.tpe match {
case ForgeType.AzureRepos => azureReposCfg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import org.scalasteward.core.edit.hooks.HookExecutor
import org.scalasteward.core.edit.scalafix._
import org.scalasteward.core.edit.update.ScannerAlg
import org.scalasteward.core.forge.github.{GitHubAppApiAlg, GitHubAuthAlg}
import org.scalasteward.core.forge.{ForgeApiAlg, ForgeRepoAlg, ForgeSelection}
import org.scalasteward.core.forge.{ForgeApiAlg, ForgeAuthAlg, ForgeRepoAlg, ForgeSelection}
import org.scalasteward.core.git.{GenGitAlg, GitAlg}
import org.scalasteward.core.io.{FileAlg, ProcessAlg, WorkspaceAlg}
import org.scalasteward.core.nurture.{NurtureAlg, PullRequestRepository, UpdateInfoUrlFinder}
Expand Down Expand Up @@ -130,7 +130,8 @@ object Context {
F: Async[F]
): F[Context[F]] =
for {
forgeUser <- config.forgeUser[F]
_ <- F.unit
forgeUser = new ForgeAuthAlg[F](config.gitCfg, config.forgeCfg).forgeUser
artifactMigrationsLoader0 = new ArtifactMigrationsLoader[F]
artifactMigrationsFinder0 <- artifactMigrationsLoader0.createFinder(config.artifactCfg)
scalafixMigrationsLoader0 = new ScalafixMigrationsLoader[F]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2018-2023 Scala Steward contributors
*
* 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.
*/

package org.scalasteward.core.forge

import cats.Monad
import cats.syntax.all._
import org.http4s.Uri.UserInfo
import org.scalasteward.core.application.Config.{ForgeCfg, GitCfg}
import org.scalasteward.core.forge.data.AuthenticatedUser
import org.scalasteward.core.io.{ProcessAlg, WorkspaceAlg}
import org.scalasteward.core.util
import org.scalasteward.core.util.Nel

final class ForgeAuthAlg[F[_]](gitCfg: GitCfg, forgeCfg: ForgeCfg)(implicit
processAlg: ProcessAlg[F],
workspaceAlg: WorkspaceAlg[F],
F: Monad[F]
) {
def forgeUser: F[AuthenticatedUser] =
for {
rootDir <- workspaceAlg.rootDir
userInfo = UserInfo(forgeCfg.login, None)
urlWithUser = util.uri.withUserInfo.replace(userInfo)(forgeCfg.apiHost).renderString
prompt = s"Password for '$urlWithUser': "
output <- processAlg.exec(Nel.of(gitCfg.gitAskPass.pathAsString, prompt), rootDir)
password = output.mkString.trim
} yield AuthenticatedUser(forgeCfg.login, password)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package org.scalasteward.core.forge

import cats.effect.Temporal
import cats.syntax.all._
import cats.{Applicative, Parallel}
import cats.{Applicative, Functor, Parallel}
import org.http4s.headers.Authorization
import org.http4s.{BasicCredentials, Header, Request}
import org.scalasteward.core.application.Config
Expand All @@ -39,7 +39,7 @@ object ForgeSelection {
def forgeApiAlg[F[_]: Parallel](
forgeCfg: ForgeCfg,
forgeSpecificCfg: ForgeSpecificCfg,
user: AuthenticatedUser
user: F[AuthenticatedUser]
)(implicit
httpJsonClient: HttpJsonClient[F],
logger: Logger[F],
Expand All @@ -64,27 +64,30 @@ object ForgeSelection {

def authenticate[F[_]](
forgeType: ForgeType,
user: AuthenticatedUser
)(implicit F: Applicative[F]): Request[F] => F[Request[F]] =
user: F[AuthenticatedUser]
)(implicit F: Functor[F]): Request[F] => F[Request[F]] =
forgeType match {
case AzureRepos => _.putHeaders(basicAuth(user)).pure[F]
case Bitbucket => _.putHeaders(basicAuth(user)).pure[F]
case BitbucketServer => _.putHeaders(basicAuth(user), xAtlassianToken).pure[F]
case GitHub => _.putHeaders(basicAuth(user)).pure[F]
case GitLab => _.putHeaders(Header.Raw(ci"Private-Token", user.accessToken)).pure[F]
case Gitea => _.putHeaders(basicAuth(user)).pure[F]
case AzureRepos => req => user.map(u => req.putHeaders(basicAuth(u)))
case Bitbucket => req => user.map(u => req.putHeaders(basicAuth(u)))
case BitbucketServer => req => user.map(u => req.putHeaders(basicAuth(u), xAtlassianToken))
case GitHub => req => user.map(u => req.putHeaders(basicAuth(u)))
case GitLab => req => user.map(u => req.putHeaders(privateToken(u)))
case Gitea => req => user.map(u => req.putHeaders(basicAuth(u)))
}

private def basicAuth(user: AuthenticatedUser): Authorization =
Authorization(BasicCredentials(user.login, user.accessToken))

private def privateToken(user: AuthenticatedUser): Header.Raw =
Header.Raw(ci"Private-Token", user.accessToken)

// Bypass the server-side XSRF check, see
// https://github.com/scala-steward-org/scala-steward/pull/1863#issuecomment-754538364
private val xAtlassianToken = Header.Raw(ci"X-Atlassian-Token", "no-check")

def authenticateIfApiHost[F[_]](
forgeCfg: ForgeCfg,
user: AuthenticatedUser
user: F[AuthenticatedUser]
)(implicit F: Applicative[F]): Request[F] => F[Request[F]] =
req => {
val sameScheme = req.uri.scheme === forgeCfg.apiHost.scheme
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import org.scalasteward.core.mock.{MockEff, MockState}

class AzureReposApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
private val user = AuthenticatedUser("user", "pass")
private val userM = MockEff.pure(user)
private val repo = Repo("scala-steward-org", "scala-steward")
private val apiHost = uri"https://dev.azure.com"

Expand Down Expand Up @@ -175,7 +176,7 @@ class AzureReposApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {

private val forgeCfg = config.forgeCfg.copy(apiHost = apiHost, tpe = ForgeType.AzureRepos)
private val azureReposCfg = AzureReposCfg(organization = Some("azure-org"))
private val azureReposApiAlg = ForgeSelection.forgeApiAlg[MockEff](forgeCfg, azureReposCfg, user)
private val azureReposApiAlg = ForgeSelection.forgeApiAlg[MockEff](forgeCfg, azureReposCfg, userM)

test("getRepo") {
val obtained = azureReposApiAlg.getRepo(repo).runA(state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.scalasteward.core.mock.{MockEff, MockState}
class BitbucketApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {

private val user = AuthenticatedUser("user", "pass")
private val userM = MockEff.pure(user)

private val basicAuth = Authorization(BasicCredentials(user.login, user.accessToken))
private val auth = HttpApp[MockEff] { request =>
Expand Down Expand Up @@ -209,7 +210,7 @@ class BitbucketApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {

private val forgeCfg = config.forgeCfg.copy(tpe = ForgeType.Bitbucket)
private val bitbucketCfg = BitbucketCfg(useDefaultReviewers = true)
private val bitbucketApiAlg = ForgeSelection.forgeApiAlg[MockEff](forgeCfg, bitbucketCfg, user)
private val bitbucketApiAlg = ForgeSelection.forgeApiAlg[MockEff](forgeCfg, bitbucketCfg, userM)

private val prUrl = uri"https://bitbucket.org/fthomas/base.g8/pullrequests/2"
private val repo = Repo("fthomas", "base.g8")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class BitbucketServerApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff]
private val repo = Repo("scala-steward-org", "scala-steward")
private val main = Branch("main")
private val user = AuthenticatedUser("user", "pass")
private val userM = MockEff.pure(user)

private val basicAuth = Authorization(BasicCredentials(user.login, user.accessToken))
private val auth = HttpApp[MockEff] { request =>
Expand Down Expand Up @@ -112,7 +113,7 @@ class BitbucketServerApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff]

private val forgeCfg = config.forgeCfg.copy(tpe = ForgeType.BitbucketServer)
private val bitbucketServerApiAlg = ForgeSelection
.forgeApiAlg[MockEff](forgeCfg, BitbucketServerCfg(useDefaultReviewers = false), user)
.forgeApiAlg[MockEff](forgeCfg, BitbucketServerCfg(useDefaultReviewers = false), userM)

test("createPullRequest") {
val data = NewPullRequestData(
Expand Down Expand Up @@ -146,7 +147,7 @@ class BitbucketServerApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff]
reviewers = Nil
)
val apiAlg = ForgeSelection
.forgeApiAlg[MockEff](forgeCfg, BitbucketServerCfg(useDefaultReviewers = true), user)
.forgeApiAlg[MockEff](forgeCfg, BitbucketServerCfg(useDefaultReviewers = true), userM)
val pr = apiAlg.createPullRequest(repo, data).runA(state)
val expected =
PullRequestOut(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.scalasteward.core.mock.{MockEff, MockState}

class GiteaApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
private val user = AuthenticatedUser("user", "pass")
private val userM = MockEff.pure(user)
private val repo = Repo("foo", "baz")

private val basicAuth = Authorization(BasicCredentials(user.login, user.accessToken))
Expand Down Expand Up @@ -61,7 +62,7 @@ class GiteaApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
tpe = ForgeType.Gitea,
apiHost = config.forgeCfg.apiHost / "api" / "v1"
)
private val giteaAlg = ForgeSelection.forgeApiAlg[MockEff](forgeCfg, GiteaCfg(), user)
private val giteaAlg = ForgeSelection.forgeApiAlg[MockEff](forgeCfg, GiteaCfg(), userM)

test("getRepo") {
giteaAlg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.scalasteward.core.mock.{MockEff, MockState}
class GitHubApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {

private val user = AuthenticatedUser("user", "pass")
private val userM = MockEff.pure(user)

private val basicAuth = Authorization(BasicCredentials(user.login, user.accessToken))
private val auth = HttpApp[MockEff] { request =>
Expand Down Expand Up @@ -198,7 +199,7 @@ class GitHubApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
private val state = MockState.empty.copy(clientResponses = auth <+> httpApp)

private val forgeCfg = config.forgeCfg.copy(tpe = ForgeType.GitHub)
private val gitHubApiAlg = ForgeSelection.forgeApiAlg[MockEff](forgeCfg, GitHubCfg(), user)
private val gitHubApiAlg = ForgeSelection.forgeApiAlg[MockEff](forgeCfg, GitHubCfg(), userM)

private val repo = Repo("fthomas", "base.g8")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.typelevel.ci.CIStringSyntax
class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {

private val user = AuthenticatedUser("user", "pass")
private val userM = MockEff.pure(user)

object MergeWhenPipelineSucceedsMatcher
extends QueryParamDecoderMatcher[Boolean]("merge_when_pipeline_succeeds")
Expand Down Expand Up @@ -127,7 +128,7 @@ class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
requiredReviewers = None,
removeSourceBranch = false
),
user
userM
)

private val gitlabApiAlgNoFork = ForgeSelection.forgeApiAlg[MockEff](
Expand All @@ -137,7 +138,7 @@ class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
requiredReviewers = None,
removeSourceBranch = false
),
user
userM
)

private val gitlabApiAlgAutoMerge = ForgeSelection.forgeApiAlg[MockEff](
Expand All @@ -147,7 +148,7 @@ class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
requiredReviewers = None,
removeSourceBranch = false
),
user
userM
)

private val gitlabApiAlgRemoveSourceBranch = ForgeSelection.forgeApiAlg[MockEff](
Expand All @@ -157,7 +158,7 @@ class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
requiredReviewers = None,
removeSourceBranch = true
),
user
userM
)

private val gitlabApiAlgLessReviewersRequired = ForgeSelection.forgeApiAlg[MockEff](
Expand All @@ -167,7 +168,7 @@ class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
requiredReviewers = Some(0),
removeSourceBranch = false
),
user
userM
)

private val gitlabApiAlgWithAssigneeAndReviewers = ForgeSelection.forgeApiAlg[MockEff](
Expand All @@ -177,7 +178,7 @@ class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
requiredReviewers = Some(0),
removeSourceBranch = false
),
user
userM
)

private val data = UpdateData(
Expand Down

0 comments on commit 556eb5c

Please sign in to comment.