From 16f5446e5e62c000d3aa5970d2d9f90460591764 Mon Sep 17 00:00:00 2001 From: Shaharia Azam Date: Sun, 1 Sep 2024 23:19:30 +0200 Subject: [PATCH] Added github custom action --- .github/workflows/test-action.yml | 17 ++++ Dockerfile | 17 +--- README.md | 26 +++++- action.yml | 22 +++++ entrypoint.sh | 4 + go.mod | 1 + go.sum | 4 + oauth2_client.go | 129 ++++++++++++++++++++++++++++++ 8 files changed, 204 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/test-action.yml create mode 100644 action.yml create mode 100644 entrypoint.sh create mode 100644 oauth2_client.go diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml new file mode 100644 index 0000000..43d7cc4 --- /dev/null +++ b/.github/workflows/test-action.yml @@ -0,0 +1,17 @@ +name: Test OAuth Mock Server Action +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup OAuth Mock Server + uses: ./ + with: + port: 8080 + client_id: test-client + client_secret: test-secret + - name: Test OAuth Mock Server + run: | + curl -I http://localhost:8080/authorize?client_id=test-client&redirect_uri=http://localhost:8081/callback&response_type=code&scope=openid%20profile%20email&state=state123 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index dc4a467..6b1033c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,12 @@ -# Start from the official Go image FROM golang:1.22-alpine -# Set the working directory inside the container WORKDIR /app -# Copy go mod and sum files COPY go.mod go.sum ./ - -# Download all dependencies RUN go mod download -# Copy the source code into the container -COPY . . - -# Build the application -RUN go build -o main . +COPY main.go ./ -# Expose the port the app runs on -EXPOSE 8080 +RUN go build -o oauth-mock-server -# Command to run the executable -CMD ["./main"] \ No newline at end of file +CMD ["./oauth-mock-server"] \ No newline at end of file diff --git a/README.md b/README.md index 51ffe72..b2e35b6 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This is a simple OAuth 2.0 mock server implemented in Go. It's designed to simul - Returns static user information - Easy to configure via environment variables - Lightweight and easy to deploy +- Available as a GitHub Action for easy integration in CI/CD workflows ## Prerequisites @@ -29,7 +30,7 @@ The server can be configured using the following environment variables: 1. Clone the repository: ``` - git clone https://github.com/yourusername/oauth-mock-server.git + git clone https://github.com/shaharia-lab/oauth-mock-server.git cd oauth-mock-server ``` @@ -70,6 +71,27 @@ The server can be configured using the following environment variables: Authorization: Bearer ``` +## Using the GitHub Action + +To use this OAuth mock server as a GitHub Action in your workflows: + +```yaml +steps: + - uses: actions/checkout@v2 + - name: Setup OAuth Mock Server + uses: shaharia-lab/oauth-mock-server@main + with: + port: 8080 + client_id: my-client + client_secret: my-secret +``` + +Replace `shaharia-lab/oauth-mock-server@main` with the actual path to the action repository and the branch or tag you want to use. + +This action sets up the OAuth mock server as a Docker container within your GitHub Actions workflow. The server will be accessible at `http://localhost:8080` (or whatever port you specify) within the GitHub Actions runner. + +You can customize the `port`, `client_id`, and `client_secret` inputs as needed for your specific use case. + ## License -This project is licensed under the MIT License. +This project is licensed under the MIT License. \ No newline at end of file diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..0a65814 --- /dev/null +++ b/action.yml @@ -0,0 +1,22 @@ +name: 'Setup OAuth Mock Server' +description: 'Sets up and runs an OAuth mock server for testing purposes' +inputs: + port: + description: 'Port to run the OAuth mock server on' + required: false + default: '8080' + client_id: + description: 'Client ID for the OAuth mock server' + required: false + default: 'test-client' + client_secret: + description: 'Client Secret for the OAuth mock server' + required: false + default: 'test-secret' +runs: + using: 'docker' + image: 'Dockerfile' + env: + PORT: ${{ inputs.port }} + CLIENT_ID: ${{ inputs.client_id }} + CLIENT_SECRET: ${{ inputs.client_secret }} \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..72908cb --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e + +./oauth-mock-server \ No newline at end of file diff --git a/go.mod b/go.mod index 38ef184..bb527f7 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.22 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/google/uuid v1.6.0 + golang.org/x/oauth2 v0.22.0 ) diff --git a/go.sum b/go.sum index 24693e9..bcfa76e 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= diff --git a/oauth2_client.go b/oauth2_client.go new file mode 100644 index 0000000..dd97d2b --- /dev/null +++ b/oauth2_client.go @@ -0,0 +1,129 @@ +// File: oauth2_client.go + +package main + +import ( + "bufio" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + + "golang.org/x/oauth2" +) + +var ( + clientID = flag.String("client_id", "test-client", "OAuth2 client ID") + clientSecret = flag.String("client_secret", "test-secret", "OAuth2 client secret") + authServerURL = flag.String("auth_server", "http://localhost:9999", "OAuth2 authorization server URL") +) + +func main() { + flag.Parse() + + config := &oauth2.Config{ + ClientID: *clientID, + ClientSecret: *clientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: *authServerURL + "/authorize", + TokenURL: *authServerURL + "/token", + }, + RedirectURL: "http://localhost:8081/callback", // This URL won't be used, but is required + Scopes: []string{"openid", "profile", "email"}, + } + + // Generate authorization URL + authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) + fmt.Printf("Please visit this URL to authorize the application: %v\n", authURL) + + // Prompt user to enter the authorization code + fmt.Print("Enter the authorization code: ") + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + code := scanner.Text() + + // Exchange authorization code for token + token, err := exchangeCodeForToken(config, code) + if err != nil { + log.Fatalf("Unable to exchange code for token: %v", err) + } + + fmt.Printf("Access Token: %s\n", token.AccessToken) + + // Get user info + userInfo, err := getUserInfo(*authServerURL, token.AccessToken) + if err != nil { + log.Fatalf("Unable to get user info: %v", err) + } + + fmt.Printf("User Info: %+v\n", userInfo) +} + +func exchangeCodeForToken(config *oauth2.Config, code string) (*oauth2.Token, error) { + values := url.Values{ + "grant_type": {"authorization_code"}, + "code": {code}, + "client_id": {config.ClientID}, + "client_secret": {config.ClientSecret}, + "redirect_uri": {config.RedirectURL}, + } + + resp, err := http.PostForm(config.Endpoint.TokenURL, values) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("token exchange failed: %s - %s", resp.Status, string(body)) + } + + var token oauth2.Token + err = json.Unmarshal(body, &token) + if err != nil { + return nil, err + } + + return &token, nil +} + +func getUserInfo(serverURL, accessToken string) (map[string]interface{}, error) { + req, err := http.NewRequest("GET", serverURL+"/userinfo", nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get user info: %s - %s", resp.Status, string(body)) + } + + var userInfo map[string]interface{} + err = json.Unmarshal(body, &userInfo) + if err != nil { + return nil, err + } + + return userInfo, nil +}