Skip to content
This repository was archived by the owner on Jun 8, 2023. It is now read-only.

github app/using-the-github app #18

Open
wants to merge 24 commits into
base: testing-push
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .env-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GITHUB_PRIVATE_KEY=""
GITHUB_APP_IDENTIFIER=
GITHUB_WEBHOOK_SECRET=
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.env
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ source 'http://rubygems.org'
gem 'sinatra', '~> 2.0'
gem 'jwt', '~> 2.1'
gem 'octokit', '~> 4.0'
gem 'dotenv'
20 changes: 11 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,36 @@ GEM
specs:
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
faraday (0.15.2)
dotenv (2.5.0)
faraday (0.15.3)
multipart-post (>= 1.2, < 3)
jwt (2.1.0)
multipart-post (2.0.0)
mustermann (1.0.2)
octokit (4.9.0)
mustermann (1.0.3)
octokit (4.13.0)
sawyer (~> 0.8.0, >= 0.5.3)
public_suffix (3.0.2)
rack (2.0.5)
rack-protection (2.0.3)
public_suffix (3.0.3)
rack (2.0.8)
rack-protection (2.0.4)
rack
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
sinatra (2.0.3)
sinatra (2.0.4)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.3)
rack-protection (= 2.0.4)
tilt (~> 2.0)
tilt (2.0.8)

PLATFORMS
ruby

DEPENDENCIES
dotenv
jwt (~> 2.1)
octokit (~> 4.0)
sinatra (~> 2.0)

BUNDLED WITH
1.14.6
1.17.1
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
This is the sample project built by following the "[Building Your First GitHub App](https://developer.github.com/apps/building-your-first-github-app)" Quickstart guide on developer.github.com.
⚠️ Note: This repository is not maintained. For more recent guides about how to build GitHub Apps, see "[About writing code for a GitHub App](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/about-writing-code-for-a-github-app)."

It consists of two different servers: `server.rb` (boilerplate) and `advanced_server.rb` (completed project).
This is an example GitHub App that adds a label to all new issues opened in a repository. You can follow the archived "[Using the GitHub API in your app](https://web.archive.org/web/20230604175646/https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/using-the-github-api-in-your-app)" guide to learn how to build the app code in `server.rb`.

## Install and run
This project listens for webhook events and uses the Octokit.rb library to make REST API calls. This example project consists of two different servers:
* `template_server.rb` (GitHub App template code)
* `server.rb` (completed project)

To run the code, make sure you have [Bundler](http://gembundler.com/) installed; then enter `bundle install` on the command line.
To learn how to set up a template GitHub App, follow the archived "[Setting up your development environment](https://web.archive.org/web/20230604175646/https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/setting-up-your-development-environment-to-create-a-github-app)" guide.

* For the boilerplate project, enter `ruby server.rb` on the command line.
## Install

* For the completed project, enter `ruby advanced_server.rb` on the command line.
To run the code, make sure you have [Bundler](https://bundler.io/) installed; then enter `bundle install` on the command line.

Both commands will run the server at `localhost:3000`.
## Set environment variables

1. Create a copy of the `.env-example` file called `.env`.
2. Add your GitHub App's private key, app ID, and webhook secret to the `.env` file.

## Run the server

1. Run `ruby template_server.rb` or `ruby server.rb` on the command line.
1. View the default Sinatra app at `localhost:3000`.
107 changes: 44 additions & 63 deletions server.rb
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
require 'sinatra'
require 'octokit'
require 'dotenv/load' # Manages environment variables
require 'json'
require 'openssl' # Used to verify the webhook signature
require 'jwt' # Used to authenticate a GitHub App
require 'time' # Used to get ISO 8601 representation of a Time object
require 'logger'
require 'openssl' # Verifies the webhook signature
require 'jwt' # Authenticates a GitHub App
require 'time' # Gets ISO 8601 representation of a Time object
require 'logger' # Logs debug statements

set :port, 3000


# This is template code to create a GitHub App server.
# You can read more about GitHub Apps here: # https://developer.github.com/apps/
#
# On its own, this app does absolutely nothing, except that it can be installed.
# It's up to you to add fun functionality!
# You can check out one example in advanced_server.rb.
#
# This code is a Sinatra app, for two reasons:
# 1. Because the app will require a landing page for installation.
# 2. To easily handle webhook events.
#
#
# Of course, not all apps need to receive and process events!
# Feel free to rip out the event handling code if you don't need it.
#
# Have fun!
#
set :bind, '0.0.0.0'

class GHAapp < Sinatra::Application

# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and read app tokens or other secrets in your code
# in a runtime source, like an environment variable like below

# Expects that the private key has been set as an environment variable in
# PEM format using the following command to replace newlines with the
# literal `\n`:
# export GITHUB_PRIVATE_KEY=`awk '{printf "%s\\n", $0}' private-key.pem`
#
# Converts the newlines
# Converts the newlines. Expects that the private key has been set as an
# environment variable in PEM format.
PRIVATE_KEY = OpenSSL::PKey::RSA.new(ENV['GITHUB_PRIVATE_KEY'].gsub('\n', "\n"))

# Your registered app must have a secret set. The secret is used to verify
Expand All @@ -54,31 +29,37 @@ class GHAapp < Sinatra::Application
end


# Before each request to the `/event_handler` route
# Executed before each request to the `/event_handler` route
before '/event_handler' do
get_payload_request(request)
verify_webhook_signature
authenticate_app
# Authenticate each installation of the app in order to run API operations
# Authenticate the app installation in order to run API operations
authenticate_installation(@payload)
end


post '/event_handler' do

# # # # # # # # # # # # # # # # # # #
# ADD YOUR CODE HERE #
# # # # # # # # # # # # # # # # # # #
case request.env['HTTP_X_GITHUB_EVENT']
when 'issues'
if @payload['action'] === 'opened'
handle_issue_opened_event(@payload)
end
end

'ok' # We've got to return _something_. ;)
200 # success status
end


helpers do

# # # # # # # # # # # # # # # # # # #
# ADD YOUR HELPERS METHODS HERE #
# # # # # # # # # # # # # # # # # # #
# When an issue is opened, add a label
def handle_issue_opened_event(payload)
repo = payload['repository']['full_name']
issue_number = payload['issue']['number']
@installation_client.add_labels_to_an_issue(repo, issue_number, ['needs-response'])
end

# Saves the raw payload and converts the payload to JSON format
def get_payload_request(request)
Expand All @@ -95,9 +76,9 @@ def get_payload_request(request)
end

# Instantiate an Octokit client authenticated as a GitHub App.
# GitHub App authentication equires that we construct a
# GitHub App authentication requires that you construct a
# JWT (https://jwt.io/introduction/) signed with the app's private key,
# so GitHub can be sure that it came from the app an not altererd by
# so GitHub can be sure that it came from the app and was not altered by
# a malicious third party.
def authenticate_app
payload = {
Expand All @@ -111,32 +92,32 @@ def authenticate_app
iss: APP_IDENTIFIER
}

# Cryptographically sign the JWT
# Cryptographically sign the JWT.
jwt = JWT.encode(payload, PRIVATE_KEY, 'RS256')

# Create the Octokit client, using the JWT as the auth token.
@app_client ||= Octokit::Client.new(bearer_token: jwt)
end

# Instantiate an Octokit client authenticated as an installation of a
# GitHub App to run API operations.
# Instantiate an Octokit client, authenticated as an installation of a
# GitHub App, to run API operations.
def authenticate_installation(payload)
installation_id = payload['installation']['id']
installation_token = @app_client.create_app_installation_access_token(installation_id)[:token]
@installation_client = Octokit::Client.new(bearer_token: installation_token)
@installation_id = payload['installation']['id']
@installation_token = @app_client.create_app_installation_access_token(@installation_id)[:token]
@installation_client = Octokit::Client.new(bearer_token: @installation_token)
end

# Check X-Hub-Signature to confirm that this webhook was generated by
# GitHub, and not a malicious third party.
#
# GitHub will the WEBHOOK_SECRET, registered
# to the GitHub App, to create a hash signature sent in each webhook payload
# in the `X-HUB-Signature` header. This code computes the expected hash
# signature and compares it to the signature sent in the `X-HUB-Signature`
# header. If they don't match, this request is an attack, and we should
# reject it. GitHub uses the HMAC hexdigest to compute the signature. The
# `X-HUB-Signature` looks something like this: "sha1=123456"
# See https://developer.github.com/webhooks/securing/ for details
# GitHub uses the WEBHOOK_SECRET, registered to the GitHub App, to
# create the hash signature sent in the `X-HUB-Signature` header of each
# webhook. This code computes the expected hash signature and compares it to
# the signature sent in the `X-HUB-Signature` header. If they don't match,
# this request is an attack, and you should reject it. GitHub uses the HMAC
# hexdigest to compute the signature. The `X-HUB-Signature` looks something
# like this: "sha1=123456".
# See https://developer.github.com/webhooks/securing/ for details.
def verify_webhook_signature
their_signature_header = request.env['HTTP_X_HUB_SIGNATURE'] || 'sha1='
method, their_digest = their_signature_header.split('=')
Expand All @@ -145,17 +126,17 @@ def verify_webhook_signature

# The X-GITHUB-EVENT header provides the name of the event.
# The action value indicates the which action triggered the event.
logger.debug "---- recevied event #{request.env['HTTP_X_GITHUB_EVENT']}"
logger.debug "---- received event #{request.env['HTTP_X_GITHUB_EVENT']}"
logger.debug "---- action #{@payload['action']}" unless @payload['action'].nil?
end

end


# Finally some logic to let us run this server directly from the commandline, or with Rack
# Don't worry too much about this code ;) But, for the curious:
# Finally some logic to let us run this server directly from the command line,
# or with Rack. Don't worry too much about this code. But, for the curious:
# $0 is the executed file
# __FILE__ is the current file
# If they are the same—that is, we are running this file directly, call the Sinatra run method
# If they are the same—that is, we are running this file directly, call the
# Sinatra run method
run! if __FILE__ == $0
end
Loading